Skip to content
Snippets Groups Projects
ale's avatar
ale authored
Test with pytest

See merge request !6
a3dd700d
History

ai-sso - a Single Sign-On implementation

EXPERIMENTAL - DO NOT USE IN PRODUCTION

This is a Single Sign-On (SSO) implementation, comprehensive of a login server and various client (middleware) modules that cover application setups such as Apache and WSGI.

Overview

The service uses cryptographically signed tickets, short-term authorization credentials generated by a central login service, whose validity can be independently verified by the authentication client.

The protocol works over HTTP, using cookies to store tickets. This makes it work out-of-the-box for browsers, but programmatic access is also possible (an example Python client is provided).

Note that, as with all cookie-based authentication systems, possession of the cookie is sufficient for authentication purposes: this makes them vulnerable to XSS and XSRF techniques. Users and application developers are expected to take the necessary preventive measures.

The login service is fully stateless and can be easily replicated (for large setups). Authentication is ultimately performed by pluggable authentication modules (PAM and LDAP support are included in the source distribution). You can write your own following the API in src/sso_server/sso_server/auth/__init__.py.

Services

A service is just an application that supports SSO. Since these are usually served over HTTP, the service specification is modeled after the URL syntax:

<HOST>[:<PORT>][/<PATH>]/

The https URL scheme is implied, thus forcing all client interactions with the system to happen over SSL. Note that the service name should always end with a slash.

Domains

Currently, a domain is simply a high-level namespace for authentication, and it is not really used except that it needs to match between the clients and the login service. It can be set to any domain name (which does not necessarily need to exist).

Implementation

This section discusses briefly the implementation details.

Protocol

Here is an example interaction between the browser, an SSO-enabled application and the login server:

1) When a client connects to a SSO-enabled application, we search for a local, service-specific, SSO cookie. If a valid cookie is found, the request will be successfully handled by the application:

https://service1.some.com

2) The application redirects the client to the SSO login server, asking it to generate a new ticket (we're skipping URL quoting for readability):

https://login.some.com/?s=service1.some.com/&d=https://service1.some.com

3) The SSO server checks for its own SSO cookie (using login.some.com/ as the service name). If the cookie is not valid, show username/password prompt page, and check the credentials against the authentication database

  1. The SSO server redirects to the original application:

    https://service1.some.com/sso_login?t=TICKET&d=https://service1.some.com/

5) The application sets the local, service-specific, SSO cookie to the value provided in the t parameter, and redirects the client to the original URL (d).

Each service supporting SSO must provide support for two special URLs:

/sso_login
Called with the t and d parameters, it should set the SSO ticket to the (base64-decoded) value of t, and redirect the client to the URL specified by d (eventually checking that it lies within its own service namespace).
/sso_logout
Clear the SSO ticket for this service and any other application-specific credentials. This handler should return an HTML page containing just the text "OK".

The login server supports the following parameters:

s - Service name

Note that all service names should end with a slash. The service name is used to generate the sso_login URL, in the following way (abusing shell syntax for clarity):

https://${SERVICE_NAME}sso_login
d - Redirect URL
When the login process is complete, redirect the user to this URL.
g - Requested group membership
A comma-separated list of group names. When authenticating the user, the login service will also check that it is a member of one or more of the specified groups, or the authentication will fail.

Visiting https://login.some.com/logout will log the user out of all the applications by calling their /sso_logout handlers (using IFRAMEs).

SSO Ticket Format

An SSO ticket is just a signed string consisting of fields separated by the pipe (|) character. It includes the ticket format version, username, service name, domain name, and group memberships.

The current format version number is 3.

Installation

To compile the software you'll need to install a few dependencies first:

  • SWIG
  • libpam
  • Apache2 development headers and libraries for your favorite threading model
  • Python development environment

On a Debian/Ubuntu system this can be accomplished with the following command (including dependencies required to run tests):

$ sudo apt-get install swig libpam0g-dev apache2-dev \
    python3-dev python3-setuptools python3-nose python3-m2crypto

Then it should be just a matter of running:

$ sh autogen.sh
$ ./configure --enable-mod-sso --enable-pam-sso
$ make install

To run the internal test suite:

$ make check

Deployment

Configuring Apache with mod_sso

You can add SSO support to Apache using mod_sso, which will be installed if you pass --enable-mod-sso to configure. It adds the SSO authentication method, which can be used with the standard require directive as follows:

<Location />
    AuthType SSO
    SSOService this.host.name/
    require valid-user
</Location>

Using require user and require group also works as expected.

Note that in some cases you might have to exclude the sso_login and sso_logout URLs from being handled by another module, for instance if you are using mod_proxy this should be added to your config:

ProxyPass !sso_login
ProxyPass !sso_logout

The following directives can be used to configure mod_sso:

SSOService
Service name for the local application. Can appear anywhere.
SSOLoginServer
URL of the login server. Must appear only once, in the top-level configuration.
SSODomain
The shared authentication domain. Must appear only once, in the top-level configuration.
SSOPublicKeyFile
Location of the file containing the login service public key.

Python / WSGI

The source distribution includes Python WSGI middleware to use for your Python web applications. An example:

from sso.middleware import SSOMiddleware
app = SSOMiddleware(app,
                    service='test.example.com/',
                    domain='example.com')

PAM

An SSO-aware PAM module is available, for integration with other services. It knows about the following parameters:

service
Service name.
domain
Shared authentication domain.
login_server
URL of the login server.
key
Location of the file containing the login service public key.

Login service

The login service is a WSGI application so you can adapt it to whatever deployment strategy is required. It can also run standalone from the command line, which is useful for debugging. In all cases, it needs a configuration file (located using the APP_CONFIG environment variable if not specified on the command line), which supports the following variables (and has Python syntax):

SSO_SECRET_KEY
Location of the file with the secret key (/etc/sso/secret.key).
SSO_PUBLIC_KEY
Location of the file with the public key (/etc/sso/public.key).
SSO_AUTH_MODULE
Module to use for authentication, specified as a Python import path (the default of sso_server.auth.auth_test should definitely be changed).
SSO_DOMAIN
Domain.
ALLOWED_SERVICES
A list of regular expression patterns defining the services which are allowed. If left unset (the default), no checks will be performed on the destination service upon authentication. Production instances should not leave this unset.
LOGIN_TICKET_TTL
Time to live for the main SSO login ticket (in seconds).
SERVICE_TICKET_TTL
Time to live for service-specific SSO tickets (in seconds).
HTML_TITLE, HTML_BANNER
A minimal attempt at customizing the user-facing login page.

You can use ssotool to generate the key pair for the login service:

$ ssotool --gen-keys --output public.key --secret-key secret.key