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
-
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