Source: nginx Severity: wishlist Tags: patch Hi, we recently did some work to make shibboleth being independent of apache. Current shibboleth package can be used to authenticate whatever server, over a fastcgi socket. The other half missing is some support into nginx. Unfortunately upstream nginx does not support fastcgi authorizers, and shibboleth has some quirks by itself so there is an external dedicated module for this at: https://github.com/nginx-shib/nginx-http-shibboleth
Can you please add it to nginx-extras? Attached is a patch against current debian packaging to include and build the module, update the copyright and the modules README. I'd be happy to see this reaching debian once we un-freeze post-jessie. Cheers, Luca -- System Information: Debian Release: 8.0 APT prefers testing APT policy: (500, 'testing'), (1, 'experimental') Architecture: amd64 (x86_64) Foreign Architectures: i386 Kernel: Linux 3.16.0-4-amd64 (SMP w/4 CPU cores) Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash Init: systemd (via /run/systemd/system)
commit 57212a99e9363e95e420542c4bd2e7645189d30e Author: Luca Bruno <lu...@debian.org> Date: Mon Jan 26 12:13:42 2015 +0100 nginx-extras: add nginx-http-shibboleth module diff --git a/debian/changelog b/debian/changelog index e5efd5b..6cd36fe 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ nginx (1.6.2-6) UNRELEASED; urgency=medium - [Michael Lustfield] + [ Michael Lustfield ] * debian/conf/sites-available/default: + Add comment about disabling gzip in HTTPS. (Closes: #773332) + Add comment about checking ssl_ciphers. (Closes: #765782) @@ -17,6 +17,10 @@ nginx (1.6.2-6) UNRELEASED; urgency=medium * debian/ngx-conf/* + Added configuration utility. (Closes: #652108) + [ Luca Bruno ] + * debian/rules: + + Added shibboleth authorizer module to nginx-extras. + -- Michael Lustfield <mich...@lustfield.net> Sun, 11 Jan 2015 14:49:36 -0600 nginx (1.6.2-5) unstable; urgency=medium diff --git a/debian/copyright b/debian/copyright index 9b123d1..c454376 100644 --- a/debian/copyright +++ b/debian/copyright @@ -89,6 +89,13 @@ Files: debian/modules/ngx_http_substitutions_filter_module/* Copyright: Copyright (C) 2014 by Weibin Yao <yaowei...@gmail.com> License: BSD-2-clause +Files: debian/modules/nginx-http-shibboleth/* +Copyright: 2013, Maxim Dounin + 2013, Nginx, Inc. + 2013-2015, David Beitey (davidjb) + 2014-2015, Luca Bruno +License: BSD-2-clause + Files: debian/* Copyright: 2007-2009, Fabio Tranchitella <kob...@debian.org> 2008, Jose Parrella <joseparre...@cantv.net> diff --git a/debian/modules/README.Modules-versions b/debian/modules/README.Modules-versions index d4bd95c..4b7a7f2 100644 --- a/debian/modules/README.Modules-versions +++ b/debian/modules/README.Modules-versions @@ -55,3 +55,7 @@ README for Modules versions ngx_http_substitutions_filter_module Homepage: https://github.com/yaoweibin/ngx_http_substitutions_filter_module Version: v0.6.4 + + nginx-http-shibboleth + Homepage: https://github.com/nginx-shib/nginx-http-shibboleth + Version: v20150121 diff --git a/debian/modules/nginx-http-shibboleth/CONFIG.rst b/debian/modules/nginx-http-shibboleth/CONFIG.rst new file mode 100644 index 0000000..c87020e --- /dev/null +++ b/debian/modules/nginx-http-shibboleth/CONFIG.rst @@ -0,0 +1,329 @@ +Configuration +============= + +.. contents:: + :local: + :backlinks: none + +Steps +----- + +#. Obtain/rebuild Shibboleth SP with FastCGI support. +#. Recompile Nginx with the ``nginx-http-shibboleth`` custom module. +#. Configure Shibboleth FastCGI authorizer and reponsder applicatons to run. +#. Configure Nginx to talk to both FastCGI authorizer and responder. +#. Configure your Nginx application ``location`` block with ``shib_request + on``. +#. Configure Shibboleth's ``shibboleth2.xml`` so the authorizer and responder are + aware of which paths to protect. +#. Ensure your application code accepts the relevant incoming headers for + authN/authZ. + +Background +---------- + +Shibboleth supports Apache and IIS by default, but not Nginx. The closest one +gets to support is via FastCGI, which Shibboleth `does have +<https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPFastCGIConfig>`_ +but the default distribution needs to be rebuilt to support it. Nginx has +support for FastCGI responders, but not for `FastCGI authorizers +<http://www.fastcgi.com/drupal/node/22#S6.3>`_. This current module, +``nginx-http-shibboleth``, bridges this gap using sub-requests within Nginx. + +The design of Nginx is such that when handling sub-requests, it currently +cannot forward the original request body, and likewise, cannot pass a +sub-request response back to the client. As such, this module does not fully +comply with the FastCGI authorizer specification. However, for Shibboleth, +these two factors are inconsequential as only HTTP redirections and HTTP +headers (cookies) are used for authentication to succeed. + + +Shibboleth SP with FastCGI Support +---------------------------------- + +For Debian-based distributions, your ``shibboleth-sp-utils`` package has +likely already been built with FastCGI support, since default repositories +feature the required FastCGI dev packages. + +For RPM-based distributions, you will either need to obtain a pre-built +package with FastCGI support or build your own. Since the ``fcgi-devel`` +libraries aren't present in RHEL or CentOS repositories, you likely require a +thirty-party repository such as EPEL (or compile from source yourself). +Recompilation of ``shibboleth-sp`` is simple, however, and an example script +can be found at https://github.com/jcu-eresearch/shibboleth-fastcgi. + + +Running the FastCGI authorizer and responder +-------------------------------------------- + +Nginx does not manage FastCGI applications and thus they must be running +before Nginx can talk to them. + +A simple option is to use `Supervisor <http://supervisord.org/>`_ or another +FastCGI controller to manage the applications. An example Supervisor +configuration to work with a rebuilt ``shibboleth-sp`` on 64-bit RHEL/CentOS +looks like:: + + [fcgi-program:shibauthorizer] + command=/usr/lib64/shibboleth/shibauthorizer + socket=unix:///opt/shibboleth/shibauthorizer.sock + socket_owner=shibd:shibd + socket_mode=0660 + user=shibd + stdout_logfile=/var/log/supervisor/shibauthorizer.log + stderr_logfile=/var/log/supervisor/shibauthorizer.error.log + + [fcgi-program:shibresponder] + command=/usr/lib64/shibboleth/shibresponder + socket=unix:///opt/shibboleth/shibresponder.sock + socket_owner=shibd:shibd + socket_mode=0660 + user=shibd + stdout_logfile=/var/log/supervisor/shibresponder.log + stderr_logfile=/var/log/supervisor/shibresponder.error.log + +Paths will need adjusting for Debian-based distributions, and the socket +locations are arbitrary. Make note of these socket locations as you will +shortly configure Nginx with them. + + +Compile Nginx with Shibboleth module +-------------------------------------- + +Compile Nginx with the ``nginx-http-shibboleth`` custom third-party module, +following instructions at http://wiki.nginx.org/3rdPartyModules. How you do +this depends on your Nginx installation processes and existing workflow. In +general, however, you can clone this module from GitHub:: + + git clone https://github.com/nginx-shib/nginx-http-shibboleth.git + +and add it into your ``configure`` step of Nginx:: + + ./configure --add-module=/path/to/nginx-http-shibboleth + +Note that you'll almost certainly have other options being passed to +``configure`` at the same time. It may be easiest to re-build Nginx from your +existing packages for your distribution, and patch the above ``configure`` +argument into the build processes. + +Also, you will likely need the Nginx module `nginx_headers_more +<http://wiki.nginx.org/HttpHeadersMoreModule>`_ in order to prevent header +spoofing from the client, unless you already have a separate solution in +place. + +If you wish to confirm the build was successful, install a version of Nginx +with debugging support, configure full trace logging, and the example +configuration below. You should notice ``shib request ...`` lines in the +output showing where ``nginx-http-shibboleth`` is up to during a request. + + +Configure Nginx +--------------- + +Nginx now needs to be configured with ``location`` blocks that point to both +the FastCGI authorizer and responder. Specify your FastCGI socket locations, +where required. Note that the ``more_clear_input_headers`` directive is +required to prevent header spoofing from the client, since the Shibboleth +variables are passed around as headers. + +.. code:: nginx + + server { + listen 443 ssl; + server_name example.org; + ... + + #FastCGI authorizer for Auth Request module + location = /shibauthorizer { + internal; + include fastcgi_params; + fastcgi_pass unix:/opt/shibboleth/shibauthorizer.sock; + } + + #FastCGI responder + location /Shibboleth.sso { + include fastcgi_params; + fastcgi_pass unix:/opt/shibboleth/shibresponder.sock; + } + + #Resources for the Shibboleth error pages. This can be customised. + location /shibboleth-sp { + alias /usr/share/shibboleth/; + } + + #A secured location. Here all incoming requests query the + #FastCGI authorizer. Watch out for performance issues and spoofing. + location /secure { + more_clear_input_headers 'Variable-*' 'Shib-*' 'Remote-User' 'REMOTE_USER' 'Auth-Type' 'AUTH_TYPE'; + + #Add your attributes here. They get introduced as headers + #by the FastCGI authorizer so we must prevent spoofing. + more_clear_input_headers 'displayName' 'mail' 'persistent-id'; + shib_request /shibauthorizer; + proxy_pass http://localhost:8080; + } + + #A secured location, but only a specific sub-path causes Shibboleth + #authentication. + location /secure2 { + proxy_pass http://localhost:8080; + + location = /secure2/shibboleth { + more_clear_input_headers 'Variable-*' 'Shib-*' 'Remote-User' 'REMOTE_USER' 'Auth-Type' 'AUTH_TYPE'; + #Add your attributes here. They get introduced as headers + #by the FastCGI authorizer so we must prevent spoofing. + more_clear_input_headers 'displayName' 'mail' 'persistent-id'; + shib_request /shibauthorizer; + proxy_pass http://localhost:8080; + } + } + } + +Notes +~~~~~ + +* ``proxy_pass`` can be replaced with any application or configuration that + should receive the Shibboleth attributes as headers. Essentially, this is + what would normally be the backend configured against ``AuthType + shibboleth`` in Apache. + +* The first 3 locations are pure boilerplate for any host that requires + Shibboleth authentication, so you may wish to template these for reuse + between hosts. + +* The ``/shibboleth-sp`` location provides web resources for default + Shibboleth error messages. If you customise error pages, or don't care for + images or styles on error pages, delete this location. + +* Take note of the ``more_clear_input_headers`` calls. As the Shibboleth + authorizer will inject headers into the request before passing the + request onto the final upstream endpoint, you **must** + use these directives to protect from spoofing. You should expand the + second call to this directive when you have more incoming attributes + from the Shibboleth authorizer. Or else beware... + +* The ``/secure`` location will ask the FastCGI authorizer for attributes for + **every** request that comes in. This may or may not be desirable. Keep in + mind this means that each request will have Shibboleth attributes add before + being sent onto a backend, and this will happen every time. + +* You may wish to consider only securing a path that creates an application + session (such as the ``/secure2`` location block), and letting your + application handle the rest. Only upon the user hitting this specific URL + will the authentication process be triggered. This is a authentication + technique to avoid extra overhead -- set the upstream for the specific + sub-path to be somewhere an application session is created, and have that + application session capture the Shibboleth attributes. + + Notice how the rest of the application doesn't refer to the authorizer. + This means the application can be used anonymously, too. Alternatively, + you can configure the ``requireSession`` option to be fa + +* Adding the ``shib_request`` line into a location isn't all you need to + do to get the FastCGI authorizer to recognise your path as Shibboleth + protected. You need also need to ensure that ``shibd`` is configured to + accept your paths as well, following the next set of instructions. + + +Configuring Shibboleth's shibboleth2.xml to recognise secured paths +------------------------------------------------------------------- + +Within Apache, you can tell Shibboleth which paths to secure by +using configuration like: + +.. code:: apache + + <Location /secure> + ShibRequestSetting authType shibboleth + ShibRequestSetting requireSession false + </Location> + +Shibboleth is made aware of this configuration automatically. + +However, the FastCGI authorizer for Shibboleth operates without such +directives and thus path protection needs to be configured like it would be +for IIS, using the ``<RequestMapper>`` configuration. The same options from +Apache are accepted within the ``RequestMapper`` section of the +``shibboleth2.xml`` configuration file, like this truncated example shows. +This example corresponds to the sample Nginx configuration given above. + +.. code:: xml + + <RequestMapper type="XML"> + <RequestMap> + <Host name="example.org" + authType="shibboleth" + requireSession="true" + redirectToSSL="443"> + <Path name="/secure" /> + <Path name="/secure2/shibboleth" /> + ... + </Host> + ... + </RequestMap> + </RequestMapper> + +Notes +~~~~~ + +* The Shibboleth FastCGI authorizer must have both ``authType`` **and** + ``requireSession`` configured for the resultant path. If they are not + present, then the authorizer will ignore the path it is passed and the user + will not be prompted for authentication (and no logging will take place). + +* ``<Path>`` names are **case sensitive**. + +* You can use other configuration items like ``<HostRegex>`` and + ``<PathRegex>`` and ``<AccessControl``> to configure how Shibboleth handles + incoming requests. + +* Configuration is inherited **downwards** in the XML tree. So, configure ``authType`` + on a ``<Host>`` element will see it apply to all paths beneath it. This is + not required, however; attributes can be placed anywhere you desire. + +* Nested ``<Path>`` elements are greedy. Putting a path with + ``name="shibboleth"`` within a path with ``name="secure"`` really translates + to a path with ``name="secure/shibboleth"``. + +* Upon changing this configuration, ensure the ``shibauthorizer`` and + ``shibresponder`` applications are hard-restarted, as well as ``shibd``. + +Gotchas +------- + +If you're experiencing issues with the Shibboleth authorizer appearing to fail +to be invoked, check the following: + +* The authorizer requires a ``<Path>`` element in ``shib2.xml`` to be + *correctly* configured with ``authType`` and ``requireSession`` for auth to + take place. If you don't (or say forget to restart ``shibd``), then the + authorizer will return a ``200 OK`` status response, which equates to + unconditionally allowing access. + +* No logs will get issued *anywhere* for anything related to the FastCGI + applications (standard ``shibd`` logging does apply, however). If you're + testing for why the authentication cycle doesn't start, try killing your + FastCGI authorizer and make sure you see a ``502`` error come back from + Nginx. If you still get a ``200``, then your ``shib_request`` configuration + in Nginx is probably wrong and the authorizer isn't being contacted. + +* When in doubt, hard restart the entire stack, and use something like ``curl`` + to ensure you avoid any browser caching. If still in doubt that the Nginx + installation has been successfully built with the ``nginx-http-shibboleth`` + module, run Nginx in debug mode, and trace the request accordingly. + + +Resources +--------- + +* http://wiki.nginx.org/HttpHeadersMoreModule +* https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPRequestMapper +* https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPRequestMap +* https://github.com/nginx-shib/nginx-http-shibboleth +* http://davidjb.com/blog/2013/04/setting-up-a-shibboleth-sp-with-fastcgi-support/ +* https://github.com/jcu-eresearch/shibboleth-fastcgi/ +* https://github.com/jcu-eresearch/nginx-custom-build + +Deprecated documentation: + +* http://davidjb.com/blog/2013/04/integrating-nginx-and-a-shibboleth-sp-with-fastcgi/ diff --git a/debian/modules/nginx-http-shibboleth/LICENSE b/debian/modules/nginx-http-shibboleth/LICENSE new file mode 100644 index 0000000..ae4d9af --- /dev/null +++ b/debian/modules/nginx-http-shibboleth/LICENSE @@ -0,0 +1,22 @@ +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/debian/modules/nginx-http-shibboleth/README.rst b/debian/modules/nginx-http-shibboleth/README.rst new file mode 100644 index 0000000..0dda562 --- /dev/null +++ b/debian/modules/nginx-http-shibboleth/README.rst @@ -0,0 +1,110 @@ +Shibboleth auth request module for nginx +======================================== + +This module allows authorization based on the result of a subrequest to +Shibboleth. Once a subrequest returns 2xx status - access is allowed; on 401 +or 403 - access is disabled with an appropriate status. + +For 40x statuses, the WWW-Authenticate header from the subrequest response +will be passed to client. All other subrequest response statuses (such as 3xx +redirects) are passed back to the client, including status and headers. This +mostly conforms to the FastCGI Authorizer specification, with the exception of +the processing of the sub-request and sub-response bodies due to limitations +in Nginx. As the Shibboleth FastCGI authorizer does not consider the contents +of a request body or use response bodies, this is not an issue. + +The module works at access phase and therefore may be combined nicely with +other access modules (access, auth_basic) via satisfy directive. + + +Configuration directives +======================== + +.. warning:: + + The ``shib_request`` directive no longer requires the ``shib_authorizer`` + flag. This must be removed for Nginx to start. No other changes are + required. + +:: + + shib_request <uri>|off + + Context: http, server, location + Default: off + + Switches the Shibboleth auth request module on and sets uri which will be + asked for authorization. The configured uri should refer to a Nginx + location block that points to your Shibboleth FastCGI authorizer. + + The HTTP status and headers of the response resulting + from the sub-request to the configured uri will be returned to the user, + in accordance with the FastCGI Authorizer + specification; see http://www.fastcgi.com/drupal/node/22#S6.3. + The one (potentially significant) caveat is that due to the way + Nginx operates at present with regards to subrequests (what + an Authorizer effectively requires), the request body will *not* be + forwarded to the authorizer, and similarly, the response body from + the authorizer will *not* be returned to the client. + + Configured URIs are not restricted to using a FastCGI backend + to generate a response, however. This may be useful during + testing or otherwise, as you can use Nginx's built in ``return`` + and ``rewrite`` directives to produce a suitable response. + Additionally, this module may be used with *any* FastCGI + authorizer, although operation may be affected by the above caveat. + + shib_request_set <variable> <value> + + Context: http, server, location + Default: none + + Set request variable to the given value after auth request completion. + Value may contain variables from auth request, e.g. $upstream_http_*. + + +Usage +===== + +:: + + # FastCGI authorizer for Shibboleth Auth Request module + location = /shibauthorizer { + internal; + include fastcgi_params; + fastcgi_pass unix:/opt/shibboleth/shibauthorizer.sock; + } + + # A secured location. All incoming requests query the Shibboleth FastCGI authorizer. + # Watch out for performance issues and spoofing. + location /secure { + more_clear_input_headers 'Variable-*' 'Shib-*' 'Remote-User' 'REMOTE_USER' 'Auth-Type' 'AUTH_TYPE'; + + # Add your attributes here. They get introduced as headers + # by the FastCGI authorizer so we must prevent spoofing. + more_clear_input_headers 'displayName' 'mail' 'persistent-id'; + + shib_request /shibauthorizer; + proxy_pass http://localhost:8080; + } + + +Installation +============ + +To compile nginx with this module, use the:: + + --add-module <path> + +option when you ``configure`` nginx. + +For further information on why this is a dedicated module, see +http://forum.nginx.org/read.php?2,238523,238523#msg-238523 + + +Configuration +============= + +For full details about configuring the Nginx/Shibboleth environment, +see +https://github.com/nginx-shib/nginx-http-shibboleth/blob/master/CONFIG.rst. diff --git a/debian/modules/nginx-http-shibboleth/config b/debian/modules/nginx-http-shibboleth/config new file mode 100644 index 0000000..a14b306 --- /dev/null +++ b/debian/modules/nginx-http-shibboleth/config @@ -0,0 +1,3 @@ +ngx_addon_name=ngx_http_shibboleth_module +HTTP_MODULES="$HTTP_MODULES ngx_http_shibboleth_module" +NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_shibboleth_module.c" diff --git a/debian/modules/nginx-http-shibboleth/ngx_http_shibboleth_module.c b/debian/modules/nginx-http-shibboleth/ngx_http_shibboleth_module.c new file mode 100644 index 0000000..c2f6d9e --- /dev/null +++ b/debian/modules/nginx-http-shibboleth/ngx_http_shibboleth_module.c @@ -0,0 +1,495 @@ + +/* + + Original ngx_http_auth_request module: + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + + Forked Shibboleth dedicated module: + * Copyright (C) 2013, David Beitey (davidjb) + * Copyright (C) 2014, Luca Bruno + + Distributed under 2-clause BSD license, see LICENSE file. + + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + ngx_str_t uri; + ngx_array_t *vars; +} ngx_http_auth_request_conf_t; + + +typedef struct { + ngx_uint_t done; + ngx_uint_t status; + ngx_http_request_t *subrequest; +} ngx_http_auth_request_ctx_t; + + +typedef struct { + ngx_int_t index; + ngx_http_complex_value_t value; + ngx_http_set_variable_pt set_handler; +} ngx_http_auth_request_variable_t; + + +static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_auth_request_done(ngx_http_request_t *r, + void *data, ngx_int_t rc); +static ngx_int_t ngx_http_auth_request_set_variables(ngx_http_request_t *r, + ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx); +static ngx_int_t ngx_http_auth_request_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static void *ngx_http_auth_request_create_conf(ngx_conf_t *cf); +static char *ngx_http_auth_request_merge_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_http_auth_request_init(ngx_conf_t *cf); +static char *ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_auth_request_commands[] = { + + { ngx_string("shib_request"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_auth_request, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("shib_request_set"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_http_auth_request_set, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_shibboleth_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_auth_request_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_auth_request_create_conf, /* create location configuration */ + ngx_http_auth_request_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_shibboleth_module = { + NGX_MODULE_V1, + &ngx_http_shibboleth_module_ctx, /* module context */ + ngx_http_auth_request_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_auth_request_handler(ngx_http_request_t *r) +{ + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h, *hi; + ngx_http_request_t *sr; + ngx_http_post_subrequest_t *ps; + ngx_http_auth_request_ctx_t *ctx; + ngx_http_auth_request_conf_t *arcf; + + arcf = ngx_http_get_module_loc_conf(r, ngx_http_shibboleth_module); + + if (arcf->uri.len == 0) { + return NGX_DECLINED; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request handler"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_shibboleth_module); + + if (ctx != NULL) { + if (!ctx->done) { + return NGX_AGAIN; + } + + /* + * as soon as we are done - explicitly set variables to make + * sure they will be available after internal redirects + */ + + if (ngx_http_auth_request_set_variables(r, arcf, ctx) != NGX_OK) { + return NGX_ERROR; + } + + /* + * Handle the subrequest + * as per the FastCGI authorizer specification. + */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request authorizer handler"); + sr = ctx->subrequest; + + if (ctx->status == NGX_HTTP_OK) { + /* + * 200 response may include headers prefixed with `Variable-` + * back into initial headers + */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request authorizer allows access"); + + part = &sr->headers_out.headers.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (h[i].hash == 0) { + continue; + } + + if (ngx_strncasecmp(h[i].key.data, + (u_char *) "Variable-", 9) == 0) { + /* copy header into original request */ + hi = ngx_list_push(&r->headers_in.headers); + + if (hi == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* Strip the Variable- prefix */ + hi->key.len = h[i].key.len - 9; + hi->key.data = h[i].key.data + 9; + hi->value = h[i].value; + + hi->lowcase_key = ngx_pnalloc(r->pool, hi->key.len); + if (hi->lowcase_key == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_strlow(hi->lowcase_key, hi->key.data, hi->key.len); + + ngx_log_debug2( + NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request authorizer copied header: \"%V: %V\"", + &hi->key, &hi->value); + } + } + + return NGX_OK; + } + + /* + * Unconditionally return subrequest response status, headers + * and content. + */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request authorizer returning sub-response"); + + r->headers_out = sr->headers_out; + return ctx->status; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_request_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); + if (ps == NULL) { + return NGX_ERROR; + } + + ps->handler = ngx_http_auth_request_done; + ps->data = ctx; + + if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, + NGX_HTTP_SUBREQUEST_WAITED) + != NGX_OK) + { + return NGX_ERROR; + } + + /* + * allocate fake request body to avoid attempts to read it and to make + * sure real body file (if already read) won't be closed by upstream + */ + + sr->request_body = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (sr->request_body == NULL) { + return NGX_ERROR; + } + + /* + * true FastCGI authorizers should always return the subrequest + * response body but the Nginx FastCGI handler does not support + * NGX_HTTP_SUBREQUEST_IN_MEMORY at present. + */ + sr->header_only = 1; + + ctx->subrequest = sr; + + ngx_http_set_ctx(r, ctx, ngx_http_shibboleth_module); + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_auth_request_done(ngx_http_request_t *r, void *data, ngx_int_t rc) +{ + ngx_http_auth_request_ctx_t *ctx = data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request done s:%d", r->headers_out.status); + + ctx->done = 1; + ctx->status = r->headers_out.status; + + return rc; +} + + +static ngx_int_t +ngx_http_auth_request_set_variables(ngx_http_request_t *r, + ngx_http_auth_request_conf_t *arcf, ngx_http_auth_request_ctx_t *ctx) +{ + ngx_str_t val; + ngx_http_variable_t *v; + ngx_http_variable_value_t *vv; + ngx_http_auth_request_variable_t *av, *last; + ngx_http_core_main_conf_t *cmcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request set variables"); + + if (arcf->vars == NULL) { + return NGX_OK; + } + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + v = cmcf->variables.elts; + + av = arcf->vars->elts; + last = av + arcf->vars->nelts; + + while (av < last) { + /* + * explicitly set new value to make sure it will be available after + * internal redirects + */ + + vv = &r->variables[av->index]; + + if (ngx_http_complex_value(ctx->subrequest, &av->value, &val) + != NGX_OK) + { + return NGX_ERROR; + } + + vv->valid = 1; + vv->not_found = 0; + vv->data = val.data; + vv->len = val.len; + + if (av->set_handler) { + /* + * set_handler only available in cmcf->variables_keys, so we store + * it explicitly + */ + + av->set_handler(r, vv, v[av->index].data); + } + + av++; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_auth_request_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "shib request variable"); + + v->not_found = 1; + + return NGX_OK; +} + + +static void * +ngx_http_auth_request_create_conf(ngx_conf_t *cf) +{ + ngx_http_auth_request_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_request_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->uri = { 0, NULL }; + */ + + conf->vars = NGX_CONF_UNSET_PTR; + + return conf; +} + + +static char * +ngx_http_auth_request_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_auth_request_conf_t *prev = parent; + ngx_http_auth_request_conf_t *conf = child; + + ngx_conf_merge_str_value(conf->uri, prev->uri, ""); + ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_auth_request_init(ngx_conf_t *cf) +{ + ngx_http_handler_pt *h; + ngx_http_core_main_conf_t *cmcf; + + cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_http_auth_request_handler; + + return NGX_OK; +} + + +static char * +ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_auth_request_conf_t *arcf = conf; + + ngx_str_t *value; + + if (arcf->uri.data != NULL) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + arcf->uri.len = 0; + arcf->uri.data = (u_char *) ""; + + return NGX_CONF_OK; + } + + arcf->uri = value[1]; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_auth_request_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_auth_request_conf_t *arcf = conf; + + ngx_str_t *value; + ngx_http_variable_t *v; + ngx_http_auth_request_variable_t *av; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + if (value[1].data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + value[1].len--; + value[1].data++; + + if (arcf->vars == NGX_CONF_UNSET_PTR) { + arcf->vars = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_auth_request_variable_t)); + if (arcf->vars == NULL) { + return NGX_CONF_ERROR; + } + } + + av = ngx_array_push(arcf->vars); + if (av == NULL) { + return NGX_CONF_ERROR; + } + + v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE); + if (v == NULL) { + return NGX_CONF_ERROR; + } + + av->index = ngx_http_get_variable_index(cf, &value[1]); + if (av->index == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + if (v->get_handler == NULL) { + v->get_handler = ngx_http_auth_request_variable; + v->data = (uintptr_t) av; + } + + av->set_handler = v->set_handler; + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[2]; + ccv.complex_value = &av->value; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/debian/rules b/debian/rules index 16f6701..d0904f9 100755 --- a/debian/rules +++ b/debian/rules @@ -111,7 +111,8 @@ extras_configure_flags := \ --add-module=$(MODULESDIR)/nginx-lua \ --add-module=$(MODULESDIR)/nginx-upload-progress \ --add-module=$(MODULESDIR)/nginx-upstream-fair \ - --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module + --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module \ + --add-module=$(MODULESDIR)/nginx-http-shibboleth %: dh $@ --with systemd