Using Shibboleth with nginx

Unlike Apache, nginx does not have a module like mod_shib interacting directly with Shibboleth daemon shibd. I will use a module ngx_http_shibboleth_module, which uses FastCGI protocol to talk to Shibboleth daemon through sockets. Shibboleth comes with two FastCGI modules:

  • FastCGI responder (shibresponder) that handles the HandlerURL
  • FastCGI authorizer (shibauthorizer) that acts as a filter and does the usual (authN, export assertions and authZ).

Of course, these modules have to be running alongside the shibd daemon. Let’s start with Shibboleth setup and configuration. I used Ubuntu 16.04 LTS (Xenial) for the setup described in the article, but it can be easily ported to other versions of Linux.

There are some guides around on how to get such configuration working, but all of them seem to be missing one or more crucial steps. This is the drawback which I tried to remedy with this article.

Installation of nginx

Xenial comes with the version 1.10.3 of nginx at the moment of this writing. This version does not support dynamic modules, which are necessary for this setup. To include another module one would have to recompile nginx with shibboleth module included. To avoid that we would rather get a newer version of nginx from their own repo.

Create a file nginx.list with the following lines and drop it into the /etc/apt/sources.list.d directory:

deb http://nginx.org/packages/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/ubuntu/ xenial nginx
deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

Import nginx signing key:

# curl -OL ‘http://nginx.org/keys/nginx_signing.key
# apt-key add nginx_signing.key

Refresh apt with apt-get update and a newer versions of nginx become available. The latest version available at the moment is 1.13.4 and we’ll go with that:

# apt-get install -t xenial nginx

For easier installation and updates you may want to pin nginx packages to nginx repo in apt preferences by dropping a file nginx with the following content into /etc/apt/preferences.d:

Package: nginx
Pin: release o=nginx
Pin-Priority: 600

Building modules

Unfortunately the modules we need are available only in source and have to be compiled before we could plug them into nginx, but fear not, there is a special tool to help out with this task. To start building we have to have a compiler installed:

# apt-get install build-essential fakeroot

Prerequisites out of the way, go to pkg-oss web site and download the latest version. Untar the file with

# tar -xzvf tip.tar.gz

In the resulting directory you will find build_module.sh script and two directories, debian and rpm. Let’s build the Shibboleth module for nginx from its source on GitHub:

# ./build_module.sh -v 1.13.4 https://github.com/nginx-shib/nginx-http-shibboleth.git

You have to run this command as root. Then in /root/debuild directory you will find couple of nice Debian packages ready for use. The one we need is nginx-module-shibboleth_1.13.4_amd64.deb. Rename debuld directory to something like shibboleth-mod-nginx.

Of course, the whole module building procedure is better carried out on a separate machine or local VM on your computer, not the production system.

Besides shibboleth module we would also need headers-more module. Let’s build it too:

# ./build_module.sh -v 1.13.4 https://github.com/openresty/headers-more-nginx-module.git

Again in the /root/debuild directory there will be a debian package we need, nginx-module-headersmore_1.13.4_amd64.deb. Rename directory debuild to something like headers-more-mod-nginx.

Now copy the Debian packages with these two modules to your nginx server and install them with

# dpkg -i nginx-module-shibboleth_1.13.4_amd64.deb nginx-module-headersmore_1.13.4_amd64.deb

In your /etc/nginx/module directory the files

ngx_http_headers_more_filter_module-debug.so  
ngx_http_headers_more_filter_module.so  
ngx_http_shibboleth_module-debug.so 
ngx_http_shibboleth_module.so

should appear.

nginx configuration

First of all, add the modules you have just installed to nginx configuration in /etc/nginx/nginx.conf

load_module modules/ngx_http_shibboleth_module.so;
load_module modules/ngx_http_headers_more_filter_module.so;

The intentionally simplified site definitions would look something like this:

server {
  listen 80 default_server;
  server_name: hostname.stanford.edu
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  root /usr/share/nginx/html

  ssl_certificate /path/to/signed_cert_plus_intermediates;
  ssl_certificate_key /path/to/private_key;
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;
  ssl_session_tickets off;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ‘EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH’;
  ssl_prefer_server_ciphers on;

  resolver 171.67.1.234 171.64.1.234 valid=300s;

# FastCGI authorizer for Shibboleth Auth Request module
  location = /shibbolethauthorizer {
    Internal;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/shibboleth/shibauthorizer.sock;
  }

# FastCGI responder for SSO
  location /Shibboleth.sso {
    include fastcgi_params;
    fastcgi_pass unix:/var/run/shibboleth/shibresponder.sock;
  }

# Location secured by Shibboleth
  location /secure {
    shib_request /shibauthorizer;
    include shib_fastcgi_params;
    include fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME
    $document_root$fastcgi_script_name;
  }
}

As you see, the configuration includes a file shib_fastcgi_params. It contains definitions of Shibboleth-related variables to be passed along to the application. Copy this file to /etc/nginx from the include directory of the shibboleth plugin distribution.

Shibboleth SP configuration

To begin with we need to install Shibboleth service provider packages:

# apt-get install shibboleth-sp2-common shibboleth-sp2-schemas shibboleth-sp2-utils

The basic Shibboleth configuration has been described in another article  as well as in the UIT documentation. I will talk through the same material very quickly.

Generate key pair to use between SP and IdP by running shib-keygen command. Download example configuration files shibboleth2.xml and attribute-map.xml and replace the existing ones in /etc/shibboleth directory. Replace PROVIDERID, EMAIL, SSLCERT, SSLKEY placeholders in shibboleth2.xml with the actual values for entity id of your service provider (https://hostname.stanford.edu/shibboleth), your email contact and the names sp-key.pem and sp-cert.pem.

Now we have to configure the FastCGI authorizer and responder services. Xenial uses systemd, so we’ll have to write some systemd units for services and sockets. Create two files shibauthorizer.socket and shibresponder.socket in /lib/systemd/system directory with the following content:

[Unit]
Description=Shibboleth FastCGI Authorizer socket

[Socket]
ListenStream=/var/run/shibboleth/shibauthorizer.sock
SocketUser=_shibd
SocketGroup=_shibd
SocketMode=0666

[Install]
WantedBy=sockets.target
[Unit]
Description=Shibboleth FastCGI Responder socket

[Socket]
ListenStream=/var/run/shibboleth/shibresponder.sock
SocketUser=_shibd
SocketGroup=_shibd
SocketMode=0666

[Install]
WantedBy=sockets.target

In the same directory create unit files for the services, shibauthorizer.service and shibresponder.service:

[Unit]
Description=Shibboleth FastCGI Authorizer
After=network.target shibd.service
Wants=shibd.service
Requires=shibauthorizer.socket

[Service]
User=_shibd
Group=_shibd
WorkingDirectory=/etc/shibboleth
ExecStart=/usr/lib/x86_64-linux-gnu/shibboleth/shibauthorizer
StandardInput=socket

[Install]
WantedBy=multi-user.target
[Unit]
Description=Shibboleth FastCGI Responder
After=network.target shibd.service
Wants=shibd.service
Requires=shibresponder.socket

[Service]
User=_shibd
Group=_shibd
WorkingDirectory=/etc/shibboleth
ExecStart=/usr/lib/x86_64-linux-gnu/shibboleth/shibresponder
StandardInput=socket

[Install]
WantedBy=multi-user.target

Enable and start the new services:

# systemctl enable shibauthorizer.socket shibauthorizer.service shibresponder.socket shibresponder.service
# systemctl start shibauthorizer.socket shibauthorizer.service shibresponder.socket shibresponder.service

Now we are ready to download metadata to deploy on the IdP. Open your browser and download it from your nginx server, like you would do with Apache:

http://hostname.stanford.edu/Shibboleth.sso/Metadata

Edit metadata if needed to add more web sites and add contact information. Upload the metadata to the IdP from Stanford SAML Configuration Manager web site. If you plan to limit access to the web site to a particular workgroup, create this workgroup and submit HelpSU ticket asking to release the eduPersonEntitlement attribute for this workgroup to your server’s entity ID.

Once metadata is installed on the IdP you should be able to test the authentication. The URL https://hostname.stanford.edu/secure/ should take you to WebAuth page and then back to the secured pages on your web site.

Restricting access

With Apache you can configure access restrictions within the site definition. Apache’s mod_shib makes this possible. There is no such possibility for nginx and we will have to use Shibboleth configuration to restrict access. For instance, to restrict access to a particular workgroup, add this fragment to shibboleth2.xml before Session parameters:

<RequestMapper type="XML">
 <RequestMap>
 <Host name="hostname.stanford.edu" authType="shibboleth" requireSession="true" redirectToSSL="443">
  <Path name="secure">
  <AccessControl>
 <Rule require="eduPersonEntitlement">stem:workgroup</Rule>
  </AccessControl>
  </Path>
 </Host>
 </RequestMap>
</RequestMapper>

You can create an elaborate logic within AccessControl element with multiple groups and user accounts and common logical operators OR AND and NOT.

Passing authentication data as server variables

Most likely you are using nginx as a front end for some application and Shibboleth is the way you are adding authentication to it. To make use of authentication data the backend application must receive it from nginx. What data is passed along is configurable in the shib_fastcgi_params, which came with the shibboleth module. It contains a number of variable definitions you can edit. For instance, there is a definition of email variable:

shib_request_set $shib_email $upstream_http_variable_email;
fastcgi_param Email $shib_email;

On the first line the data in the request is coming from shibboleth as $upstream_http_variable_email variable. In this long name $upstream_http_variable_ is a prefix and “email” is the actual ID of the shibboleth attribute, which you find in /etc/shibboleth/attribute-map.xml:

<Attribute name="urn:mace:dir:attribute-def:mail" id="mail"/>

You probably noticed that in the attribute-map.xml the name of the email attribute is actually “mail”, not “email”. You are right, we have to fix that either in shibboleth2.xml or in shib_fastcgi_params. They have to match:

shib_request_set $shib_email $upstream_http_variable_mail;
fastcgi_param Email $shib_email; 

The same goes for other attributes, which in our environment may have different IDs then in the environment of the author of the shibboleth module. For instance, on Stanford IdP the group membership attribute is named eduPersonEntitlement, so you have to change

shib_request_set $shib_entitlement $upstream_http_variable_entitlement;
fastcgi_param Entitlement $shib_entitlement;

to

shib_request_set $shib_entitlement $upstream_http_variable_edupersonentitlement;
fastcgi_param Entitlement $shib_entitlement;

Note that the word after “fastcgi_param” is the name of the server variable that is passed along to the application and you may also want to edit it to give it a value which your application expects to find. After adjusting the parameters be sure to reload nginx.

Testing

If you are doing this setup for the first time, there is a good chance that you are trying it on a test server. I think that an easy and straightforward way to test passing of the server variables is just running phpinfo() function. To use it, of course, you have to setup PHP on your server:

# apt-get install php7.0-fpm

Nginx would talk to php using FastCGI socket. So you’ll need to add lines

fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;

to the definition of your secure location in nginx.conf. In the root of the web site create a simple index.php with the line:

<?php phpinfo(); ?>

Go to your protected location in the browser: https://hostname.stnaford.edu/secure/, authenticate, and observe the variables passed to PHP in the section “PHP Variables”.

Passing along request headers

Some applications may not be capable of using server variables for authentication or, more commonly, nginx is used as a reverse proxy in front of the application. In such cases the shibboleth module provides an option to pass along the authentication information in the request headers. Of course we have to be careful here to avoid spoofing as it is really easy to do. To protect from spoofing we can use headers_more nginx module, which we have built earlier, remember? It can be used to clear all authentication-related request headers prior to authentication ensuring that they are set by shibboleth authorizer. Corresponding nginx configuration for secure location would look like this:

location /secure {
 shib_request /shibauthorizer;
 shib_request_use_headers on;
 include shib_clear_headers;
 more_clear_input_headers 'displayName' 'mail' 'eduPersonEntitlement';
 proxy_pass http://localhost:8080;
}

shib_clear_headers is a list of all Shibboleth attributes. Be sure to add or edit the ones you defined in shib_fastcgi_params. In the attributes list after more_clear_input_headers directive list all the attributes which your application uses. The example of the shib_clear_headers file can also be found in the include directory of the shibboleth module distribution.

Conclusion

That was easy, wasn’t it?