Name Last Update
authserv Loading commit data...
debian Loading commit data...
pam Loading commit data...
.gitignore Loading commit data...
.gitlab-ci.yml Loading commit data...
COPYING Loading commit data...
README.md Loading commit data...
setup.py Loading commit data...
tox.ini Loading commit data...

A/I Auth Server

An authentication server with an LDAP backend and some advanced features:

  • OTP support
  • application-specific passwords
  • rate limiting and brute force protection
  • user partitioning across multiple backends

The authentication server can be queried using an HTTP API. The software includes a PAM module (pam_authclient) that can be used to integrate system services.

Configuration

The authentication server data model is based on the concept of a user account. The server knows how to retrieve user accounts stored in LDAP, but it has to be told the specific details of how to find them and how to map the information there to what it needs.

The server's behavior can be configured with a Python file defining some module-level variables:

  • LDAP_URI is the LDAP connection string.
  • LDAP_BIND_DN and LDAP_BIND_PW specify the bind credentials: they should allow the server to read authentication-related attributes on the user objects, and to search the user subtree.
  • LDAP_SERVICE_MAP is a {service: query} dictionary that defines how to query the LDAP database for user accounts, for each service.
  • LDAP_SCHEMA is a dictionary that describes how to retrieve authentication information from the LDAP object attributes.

Query definition

LDAP queries are meant to return a single user account object from the database using a search operation. They are represented as dictionaries, with the following fields:

  • dn: specifies the full DN of the object
  • base: specifies a base DN for the search
  • filter: specifies a filter to apply to the search

The dn and base attributes are mutually exclusive. In fact, queries can be of two types:

  • If you can know the specific DN of a user account object given its username, specify the dn field. This will run a SCOPE_BASE query for that specific DN. In this case, filter is optional.
  • Specify base and filter together to identify a single object. This will result in a SCOPE_SUBTREE search starting at base. In this case the filter attribute is required.

On every incoming request, the query fields are subject to string substitution, using the standard Python syntax of the % operator, with the following client-provided variables available:

  • user: username
  • service: authentication service
  • shard: user shard

So, for example, a mail service could use the following query:

{
  'base': 'ou=Accounts,dc=example,dc=com',
  'filter': 'mail=%(user)s'
}

or alternatively, if the database structure is simple enough:

{
  'dn': 'mail=%(user)s,ou=Accounts,dc=example,dc=com'
}

Schema definition

In order to retrieve authentication information from the LDAP object, the authentication server needs to know which attributes to use. To do so, we use a so-called schema definition (a map of symbolic names to LDAP attributes). The following attribute names are defined:

  • password: attribute containing the encrypted password. Usually (and by default) this is userPassword, a somewhat standard LDAP attribute. Since this attribute is often also used for authentication of the LDAP protocol itself, an eventual {crypt} prefix is ignored. Passwords should be encrypted with the system crypt method.
  • otp_secret: this attribute should contain the hex-encoded TOTP secret.
  • app_specific_password: attribute (possibly defined more than once) containing an encoded app-specific password.
  • shard: if set, LDAP attribute containing a shard ID for user partitioning.

The LDAP backend module makes some assumptions on the structure of the user database: the most important is that app-specific passwords are attributes of the user object. App-specific passwords should be encoded as colon-separated strings:

service:encrypted_password:comment

Again, the password should be encrypted with the system crypt method. The comment is a free-form string set by the user to tell the various credentials apart.

OTP implementation

The authentication server uses a very simple implementation of time-based OTP (TOTP), supporting a single secret per user and without any fancy features such as emergency tokens etc. The reason for this is that TOTP authentication requires just plain read-only access to the user database, while counter-based authentication with proper token revocation is a read-write, locked operation which is more difficult to perform on a LDAP backend.

Usage

Client authentication

Requests to the authentication server can themselves be authenticated, and it may be a good idea to do so in a production environment.

Authserv supports TLS-based authentication using a private CA: in order to perform authentication queries, a client must present an X509 certificate signed by a specific Certification Authority.

This feature is enabled by passing the --ca, --ssl-cert and --ssl-key options to the server.

The PAM client has options to specify a client certificate.

HTTP API

The authentication server accepts HTTP POST requests at the /api/1/auth URL. Requests should contain the following parameters (with a Content-Type of application/x-www-form-urlencoded):

  • service: the service to authenticate for
  • username: username
  • password: password
  • otp_token: a 6-digit TOTP token (optional)
  • source_ip: the remote address of the client connection (optional)
  • shard: server shard (optional)

The response is a simple string, one of:

  • OK: the authentication request was successful
  • ERR_AUTHENTICATION_FAILURE: the authentication request failed
  • ERR_OTP_REQUIRED: an OTP token is required in order to authenticate, but it was not provided in the request.

If the client application receives an ERR_OTP_REQUIRED result, it should prompt the user for an OTP token and retry the authentication request (including the same username and password) with the otp_token field set. The PAM module, for example, can ask the user for the OTP token interactively, while web applications could display an interstitial form.

PAM

A PAM module, pam_authclient, is provided to integrate system services with the authentication server. It provides only the account (a no-op) and auth operations.

Example usage:

auth required pam_authclient.so auth_server=127.0.0.1:1616

The module knows about the following options:

  • auth_server=HOST:PORT specifies the authentication server to talk to. It's possible to specify more than one server, separated by commas, in which case they will be tried in sequence in case of failure.
  • ssl_crt=FILE load the X509 client certificate from FILE (in PEM format).
  • ssl_key=FILE load the X509 certificate key from FILE.
  • ca=FILE should point at the X509 CA certificate, used to verify the authenticity of the server connection: the server certificate must be signed by this CA.
  • shard=ID sets the shard parameter, which may be used by the authentication server to limit authentication to a subset of users.

The service parameter sent to the auth server will be the PAM service, it is not possible to override this in the PAM configuration.

NGINX mail_http_auth support

The authentication server can optionally offer an HTTP API compatible with NGINX's mail_http_auth module. This is particularly useful in combination with user partitioning, as the server can use the shard attribute of the user to direct NGINX to the right backend.

The module understands the following configuration variables:

  • NGINX_AUTH_SERVICE (default: mail) is the service that will be used for authentication by this module.
  • NGINX_AUTH_PORT_MAP is a dictionary that maps protocol names to port numbers for the backend connection. The default is {"pop3": 110, "imap": 143}.