Design of the Debian SSO guest backend
The guest backend solution consists of two parts: a database for storing account data and a frontend for self-management for guest users. The solution should provide the following functionality:
- users should be able to register a guest user account
- existing guest users should be able to change their passwords
- existing guest users should be able to change their profile information
- existing guest users should be able to reset lost or forgotten passwords
- admins should be able to manage guest user accounts in case of malfunctions
- the current and any future SSO service should be able to authenticate users against the guest backend
Because the existing Debian SSO already uses LDAP (Leightweight Directory Access Protocol1) to authenticate Debian Developers and because LDAP is more or less a standard for directory services, it stands to reason to also use LDAP as a database backend for storage of user accounts.
LDAP stores data in a Tree using objects with attributes and values. The set of attributes are defined in the objectclasses the object itself has as attributes. An LDAP object is uniquely identified by its Distinguished Name, which can be described as the path to the object from the databases root entry.
The directory structure of the backend could be very simple- as there are only accounts stored in the database and no groups or addressbooks (which is what ldap is used for often).
The Domain Componenet of the LDAP tree should reflect the infrastructure it is running on, i.e. dc=guestldap,dc=debian,dc=org if hosted on Debian infra or dc=guestldap,dc=debian,dc=net if hosed somewhere else. The container for the accounts should follow the best practice and be called ou=people.
A guest accounts basically consists of a username and a password hash. In LDAP there are various objectclasses that provide these attributes, like posixAccount or shadowAccount. Both are described in rfc23072. Every objectClass has attibutes the object MUST have and attributes it MAY have. For our usecase the objectclass shadowAccounts is a better fit, because posixAccount has multiple attibutes we would have to store that are not useful in our setup (i.e. uid, gid, homeDirectory).
The shadowAccount objectclass has the following definition3:
objectClasses: ( 220.127.116.11.18.104.22.168 NAME 'shadowAccount' DESC 'Additional attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ description ) )
So by using this objectclass we have to only use the uid and it also provides the attribute userPassword for storing the password hash of the users password.
The requirements at the beginning of this document also specify that the user should be able to reset the password for their account. This means we have to store some kind of account reset information. It is popular to use an email address for these kind of situations. As apparent from the objectclass defintion above, the shadowAccount objectclass does not provide an email field. Thus we have to include another objectclass in the guest account entries. There are multiple objectclasses that provide an email attribute. One often used objectclass is inetOrgPerson, which is defined in rfc27984:
objectclass ( 2.16.840.1.113722.214.171.124 NAME 'inetOrgPerson' DESC 'RFC2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )
inetOrgPerson provides the mail attribute. The objectclass has no MUST attributes, but it is derived from organisationalPerson which is itself derived from person, which has the attributes cn (commonName) and sn (surname) as MUST attributes.
In conclusion, a guest user entry would have to have the following entries:
The ldap server should not allow anonymous bind. The password hash in the userPassword attribute should use a current hashing algorithm. OpenLDAP provides built in support for MD5, MD5 salted, SHA-1 and SHA-1 salted, which are all not recommended anymore. There is the possibilty to pass hashing and verification to the operating systems crypt5 function though. In linux the crypt function can use different hashing schemes. The hashing scheme used for the password hash in prepended to the password hash and forms a PHC string as defined in the PHC string format6. (I’ve asked DSA for information about preferred hashing algorithms on Debian systems, but got no answer back.)
As described in the preamble, users should be able to register an account and be able to do basic self service, like change the password and reset the password in case it gets lost. Also there should be an admin interface for user management.
Most users are accustomed to do such tasks via a webinterface, which is also the most common solution. There are multiple techonolgies out there that could help in writing such a webinterface- the most common solutions in the Debian project are perl scripts and Django7 based webprojects (like tracker.d.o).
Django is a web framework that is based on the Model-View-Controller pattern. It provides the building blocks to easily create basic Webapplications. Moreover it comes with an integrated admin interface and user and group models.
Djangos default database backends include MySQL, sqlite and other relational databases, but not LDAP. There are two approaches to use Django with LDAP:
It is possible to use Django plugins for authenticating against an LDAP database (like python-django-auth-ldap or python-django-python3-ldap). The problem with these plugins is, that they are only for authentication, which means that any write operations on the LDAP Database would have to be programmed by hand. The upside is, that this approach provides a more granular control on the permissions the Django application needs for working with the LDAP Backend.
The second approach would be to use django-ldapdb8, which uses LDAP as a data storage backend. The benefit of this approach is a better integration in the Django framework (i.e. the user model could be reused which makes it easily accessible in the Django admin console). The downside is, that the Django application need read and write access to the whole ou=people subtree, to be able to perform its tasks. django-ldapdb is not packaged for Debian yet.
Webinterface and Backend-Database
The Webinterface and the LDAP Database can basically run on the same server, but the services can also be distributed to to servers. Regardless of the servers, the Webinterface should access the LDAP Database via an encrypted channel (ldaps).
- https://tools.ietf.org/html/rfc4511 [return]
- https://tools.ietf.org/html/rfc2307 [return]
- http://www.zytrax.com/books/ldap/ch3/ [return]
- https://tools.ietf.org/html/rfc2798 [return]
- https://manpages.debian.org/stretch/manpages-dev/crypt.3.en.html [return]
- https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md [return]
- https://www.djangoproject.com/ [return]
- https://github.com/django-ldapdb/django-ldapdb/ [return]