SSO as a Service (SSOaaS)

Our concept of SSOaaS

Access management provides 3 services:

  • Global Authentication: Single Sign-On

  • Authorization: Grant authentication is not enough. User rights must be checked

  • Accounting: SSO logs (access) + application logs (transactions and results)

LL::NG affords all these services (except application logs of course, but headers are provided to allow this).

Headers setting is an another LL::NG service. LL::NG can provide any user attributes to an application (see Rules and headers)

*aaS means that application can drive underlying layer (IaaS for infrastructure, PaaS for platform,…). So for us, SSOaaS must provide the ability for an application to manage authorizations and choose user attributes to receive. Authentication can not be really *aaS: application can just use it but not manage it.

LL::NG affords some features that can be used for providing SSO as a service. So a web application can manage its rules and headers. Docker or VM images (Nginx only) includes LL::NG Nginx configuration that aims to a Central LL::NG authorization server. By default, all authenticated users can access and just one header is set: Auth-User. If application defines a RULES_URL parameter that refers to a JSON file, authorization server will read it, apply specified rules and set required headers (see DevOps Handler).

Two different kinds of architecture are existing to do this:

Note

Some requests can be dropped by the central FastCGI/uWSGI server.

Example below with an uWSGI server to prevent Load Balancer health check requests being forwarded to the central DevOps Handler

route-remote-addr = ^127\.0\.0\.25[34]$ break: 403 Forbidden for IP ${REMOTE_ADDR}

Example of a Central FastCGI architecture:

image0

In both case, Handler type must be set to DevOps.

Examples of webserver configuration for Docker/VM images

Using a Central FastCGI (or uWSGI) Server

Nginx

Examples below are customized web server templates for requesting authorization from a Central FastCGI server. You can replace ‘fastcgi_*’ directives by ‘uwsgi_*’ for requesting a Central uWSGI server (Nginx only):

server {
  listen <port>;
  server_name myapp.domain.com;
  root /var/www/myapp;
  index index.php;

  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;

    # Handler directive to declare this VHost as DevOps and
    # Pass authorization requests to central FastCGI server
    fastcgi_param VHOSTTYPE DevOps;
    # or to declare DevOpsST handler
    # fastcgi_param VHOSTTYPE DevOpsST;
    fastcgi_pass 10.1.2.3:9090;

    # Drop post data
    fastcgi_pass_request_body off;
    fastcgi_param CONTENT_LENGTH "";

    # Keep original request (LL::NG server will receive /lmauth)
    fastcgi_param X_ORIGINAL_URI  $original_uri;

    # Keep original hostname
    fastcgi_param HOST $http_host;

    # Set redirection parameters
    fastcgi_param HTTPS_REDIRECT "$https";
    fastcgi_param PORT_REDIRECT $server_port;

    # This URL will be fetched by the Central FastCGI server every 10 mn and
    # then used for compliling access rules and headers relative to this VirtualHost
    # CHECK THAT IT CAN BE REACHED BY THE CENTRAL FASTCGI SERVER
    fastcgi_param RULES_URL http://myapp.domain.com/rules.json;
    # or to use an external rules server
    # fastcgi_param RULES_URL http://rulesserver/my.json;

  }

  location /rules.json {
    auth_request off;
    allow 10.1.2.3;
    deny all;
  }

  # Example with php-fpm:
  location ~ ^(.*\.php)$ {
    auth_request /lmauth;
    set $original_uri $uri$is_args$args;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    include /etc/nginx/nginx-lua-headers.conf;
    # ...
    # Example with php-fpm
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
  }

  # Example as Reverse-Proxy:
  location /api/ {
    auth_request /lmauth;
    set $original_uri $uri$is_args$args;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;
    include /etc/nginx/nginx-lua-headers.conf;
    # ...
    proxy_pass http://myapp.dev.com:8081/;
  }

  location / {
    try_files $uri $uri/ =404;
  }
}

Apache

LemonLDAP::NG provides a dedicated FastCGI client (Lemonldap::NG::SSOaaS::Apache::Client) which is an alternative to L<Lemonldap::NG::Handler::ApacheMP2> that replaces main handler. It calls a remote LemonLDAP::NG FastCGI server to get authentication, authorization and headers. You have to install Lemonldap::NG::SSOaaS::Apache::Client (LemonLDAP::NG FastCGI client), FCGI::Client (Perl FastCGI dependency) and Mod_Perl2 (Apache module used for parsing HTTP headers). Then, add this in your apache2.conf web applications or Reverse-Proxies.

<VirtualHost port>
    ServerName myapp.domain.com
    DocumentRoot "/var/www/myapp"
    ErrorLog /var/log/apache2/localsite_error.log
    CustomLog /var/log/apache2/localsite_access.log combine

    <Location /rules.json>
      Order deny,allow
      Deny from all
      Allow from 10.1.2.3
    </Location>

    <LocationMatch "^/(?!rules.json)">
      PerlHeaderParserHandler Lemonldap::NG::SSOaaS::Apache::Client

      # Handler directive to declare this VHost as DevOps and
      # Pass authorization requests to Central FastCGI server
      PerlSetVar VHOSTTYPE DevOps
      # or to declare DevOpsST handler
      # PerlSetVar VHOSTTYPE DevOpsST
      PerlSetVar LLNG_SERVER 10.1.2.3:9090

      # Keep original hostname
      PerlSetVar HOST HTTP_HOST

      # Set redirection parameters
      PerlSetVar HTTPS_REDIRECT HTTPS
      PerlSetVar PORT_REDIRECT SERVER_PORT

      # This URL will be fetched by the Central FastCGI server every 10mn and
      # then used for compliling access rules and headers relative to this VirtualHost
      # CHECK THAT IT CAN BE REACHED BY THE CENTRAL FASTCGI SERVER
      PerlSetVar RULES_URL http://myapp.domain.com/rules.json
      # or to use an external rules server
      # PerlSetVar RULES_URL http://rulesserver/my.json;
    </LocationMatch>
</VirtualHost>

Node.js

Using express and fastcgi-authz-client, you can also protect an Express server. Example:

var express = require('express');
var app = express();
var FcgiAuthz = require('fastcgi-authz-client');
var handler = FcgiAuthz({
  host: '127.0.0.1',
  port: 9090,
  PARAMS: {
    RULES_URL: 'http://my-server/rules.json'
    HTTPS_REDIRECT: 'ON',
    PORT_REDIRECT: '443'
  }
});

app.use(handler);

// Simple express application
app.get('/', function(req, res) {
  return res.send('Hello ' + req.upstreamHeaders['auth-user'] + ' !');
});

// Launch server
app.listen(3000, function() {
  return console.log('Example app listening on port 3000!');
});

Plack application

You just have to enable Plack::Middleware::Auth::FCGI. Simple example:

use Plack::Builder;

my $app   = sub {
  my $env = shift;
  my $user = $env->{fcgiauth-auth-user};
  return [ 200, [ 'Content-Type' => 'text/plain' ], [ "Hello $user" ] ];
};

# Optionally ($fcgiResponse is the PSGI response of remote FCGI auth server)
#sub on_reject {
#    my($self,$env,$fcgiResponse) = @_;
#    my $statusCode = $fcgiResponse->{status};
#    ...
#}

builder
{
  enable "Auth::FCGI",
    host => '127.0.0.1',
    port => '9090',
    fcgi_auth_params => {
      RULES_URL => 'https://my-server/rules.json',
      HTTPS_REDIRECT => 'ON',
      PORT_REDIRECT  => 443
    },
    # Optional rejection subroutine
    #on_reject => \&on_reject;
    ;
  $app;
};

Using front Reverse-Proxies

This is a simple Nginx configuration file. It looks like a standard LL::NG Nginx configuration file except for:

  • VHOSTTYPE parameter forced to use DevOps handler

  • /rules.json do not have to be protected by LL::NG but by the web server itself.

This configuration handles *.dev.sso.my.domain URL and forwards authenticated requests to <vhost>.internal.domain. Rules can be defined in /rules.json which is located at the website root directory.

server {
  listen <port>;
  server_name "~^(?<vhost>.+?)\.dev\.sso\.my\.domain$";
  location = /lmauth {
    internal;
    include /etc/nginx/fastcgi_params;
    fastcgi_pass unix:/var/run/llng-fastcgi-server/llng-fastcgi.sock;

    # Force handler type:
    fastcgi_param VHOSTTYPE DevOps;

    # Drop post data
    fastcgi_pass_request_body  off;
    fastcgi_param CONTENT_LENGTH "";

    # Keep original hostname
    fastcgi_param HOST $http_host;

    # Keep original request (LL::NG server will received /lmauth)
    fastcgi_param X_ORIGINAL_URI  $original_uri;

    # Set redirection params
    fastcgi_param HTTPS_REDIRECT "$https";
    fastcgi_param PORT_REDIRECT $server_port;
  }

  location /rules.json {
    auth_request off;
    allow 127.0.0.0/8;
    deny all;
  }

  location / {
    auth_request /lmauth;
    set $original_uri $uri$is_args$args;
    auth_request_set $lmremote_user $upstream_http_lm_remote_user;
    auth_request_set $lmlocation $upstream_http_location;
    error_page 401 $lmlocation;

    include /etc/nginx/nginx-lua-headers.conf;

    proxy_pass https://$vhost.internal.domain;
  }
}