Can nginx support FastCGI Authorizers?

I search the wiki of nginx,but can’t find any thing about FastCGI
Authorizers.
can or How to use FastCGI Authorizers under nginx?

see also : http://www.fastcgi.com/devkit/doc/fcgi-spec.html

6.3 Authorizer

An Authorizer FastCGI application receives all the information
associated with an HTTP request and generates an
authorized/unauthorized decision. In case of an authorized decision
the Authorizer can also associate name-value pairs with the HTTP
request; when giving an unauthorized decision the Authorizer sends a
complete response to the HTTP client.

Since CGI/1.1 defines a perfectly good way to represent the
information associated with an HTTP request, Authorizers use the same
representation:

* The Authorizer application receives HTTP request information

from the Web server on the FCGI_PARAMS stream, in the same format as a
Responder. The Web server does not send CONTENT_LENGTH, PATH_INFO,
PATH_TRANSLATED, and SCRIPT_NAME headers.

* The Authorizer application sends stdout and stderr data in the

same manner as a Responder. The CGI/1.1 response status specifies the
disposition of the request. If the application sends status 200 (OK),
the Web server allows access. Depending upon its configuration the Web
server may proceed with other access checks, including requests to
other Authorizers.

  An Authorizer application's 200 response may include headers

whose names are prefixed with Variable-. These headers communicate
name-value pairs from the application to the Web server. For instance,
the response header

          Variable-AUTH_METHOD: database lookup

  transmits the value "database lookup" with name AUTH-METHOD. The

server associates such name-value pairs with the HTTP request and
includes them in subsequent CGI or FastCGI requests performed in
processing the HTTP request. When the application gives a 200
response, the server ignores response headers whose names aren’t
prefixed with Variable- prefix, and ignores any response content.

  For Authorizer response status values other than "200" (OK), the

Web server denies access and sends the response status, headers, and
content back to the HTTP client.

2008/12/27 lhmwzy [email protected]:

hmm…
Has Igor any plan to support FastCGI Authorizers?

2008/12/28 Maxim D. [email protected]:

Hello!

On Sat, Dec 27, 2008 at 08:30:09PM +0800, lhmwzy wrote:

I search the wiki of nginx,but can’t find any thing about FastCGI Authorizers.
can or How to use FastCGI Authorizers under nginx?

Only FastCGI responder role is supported.

Maxim D.

Has Igor any play for this?

2008/12/29 André Cruz [email protected]:

+1 for FastCGI Authorizers. This way we can use nginx with a
Shibboleth SP.

André Cruz

On Tue, 06 Jan 2009 14:52:24 +0000, André Cruz wrote:

I don’t think so. I’ve had no feedback on this.
Couldn’t you use a per or lua or wsgi hook in the mean time? I believe
those can handle auth, correct? otherwise, there’s always the source…
I’m pretty sure you could do it with a module, although you may need to
implement new hooks into request processing, as if you want to integrate
it with libevent (i.e. smartly), you will probably need to add the hooks
into a request to wait on authorizers.

/K

I don’t think so. I’ve had no feedback on this.

André Cruz wrote:

This is probably better done in the fastcgi module itself. After all,
it is a part of the fcgi specification that is not implemented.

I’ve thought about this in the past and wondered whether the best way to
do it would be with a module along the lines of “auth_fastcgi”. Is this
viable? Does anyone have any general views on how such a module would
need to be structured?

Cheers,
Igor C.

On Jan 9, 2009, at 1:05 , Bryon Roche wrote:

On Tue, 06 Jan 2009 14:52:24 +0000, André Cruz wrote:

I don’t think so. I’ve had no feedback on this.
Couldn’t you use a per or lua or wsgi hook in the mean time? I
believe
those can handle auth, correct? otherwise,

Maybe, but the applications that I would like to use only support fcgi
authorizers.

there’s always the source…
I’m pretty sure you could do it with a module, although you may need
to
implement new hooks into request processing, as if you want to
integrate
it with libevent (i.e. smartly), you will probably need to add the
hooks
into a request to wait on authorizers.

This is probably better done in the fastcgi module itself. After all,
it is a part of the fcgi specification that is not implemented.

Andr

On Sun, Dec 28, 2008 at 09:03:55AM +0800, lhmwzy wrote:

hmm…
Has Igor any plan to support FastCGI Authorizers?

Currently there is no plans to support this.
Initually FastCGI support was written to meet PHP FastCGI support.
And PHP may be FastCGI Responder only.

Do other FastCGI servers support FastCGI Authorizer role?

Apache and lighttpd both do authorizer mode. Don’t know about others…

lighttpd web server and zeus web server both support FastCGI
Authorizers.
FastCGI servers? what’s your mean?

2009/1/12 Igor S. [email protected]:

On Mon, Jan 12, 2009 at 07:24:09PM +0800, lhmwzy wrote:

lighttpd web server and zeus web server both support FastCGI Authorizers.
FastCGI servers? what’s your mean?

I mean FastCGI-backend - PHP, Django, etc.
nginx, lighty, Zeus, and Apache are just proxies that convert a HTTP
request
to FastCGI one and pass the request to a real FastCGI-server for
processing.

You can look at the way how lighttpd implements FastCGI Authorizers.

2009/1/12 Igor C. [email protected]:

On Jan 12, 2009, at 11:34 , Igor S. wrote:

processing.
If you mean applications that take advantage of fastcgi authorizers I
know that at least Shibboleth (Internet2’s Single-Sign on solution)
uses it. In fact it’s the only way to use nginx as a Service Provider
frontend.

Andr

I use FastCGI Authorizers to exam the valid of user and protect some
especial directory.
I use perl to implement it.
the script:

#!/usr/bin/perl

use strict;

use DBI;
use FCGI;

use constant PATH_NEVER => 0;
use constant PATH_MAYBE => 1;
use constant PATH_ALWAYS => 2;

normally, I would abstract this stuff out into a sitewide config

module,

but for didactic reasons, I’ll just define some constants here:

use constant COOKIE_NAME => ‘AUTH_TOKEN’;
use constant DBI_DSN =>
‘dbi:mysql:hostname=DBHOSTNAME;database=DBNAME’;
use constant DBI_USR => ‘monty’;
use constant DBI_PWD => ‘widenius’;

use constant AUTH_ERR => -1;
use constant AUTH_NOK => 0;
use constant AUTH_OK => 1;

use constant AUTH_QUERY => <<AQ_SQL;
select count(*) as authorized
from login_table
where user = ?
and token = ?
and expiry > unix_timestamp()
AQ_SQL
use vars qw($DBH $STH $N);

sub _init ();
sub _exit ();
sub authorized ($$$);
sub get_login_cookie ();
sub query_decode (;@);

_init();

for ($N = 0; FCGI::accept() >= 0; $N++)
{
# check the path to see if we want/need to authorize access:
my $path_auth = check_path($ENV{REQUEST_URI});

if($path_auth == PATH_MAYBE)
{
    my $auth = undef();
    my $user;
    my $token;
    my $cookie;

    # get the login cookie and decompose it into user + token:
    # cookie format: USERNAME:OPAQUETOKENFROMDB
    $cookie = get_login_cookie();
    ($user,$token) = split(/:/,$cookie,2);

    # check to see if an unexpired entry exists in the db:
    $auth = authorized($STH, $user, $token);

    if($auth == AUTH_OK)
    {
        # return 200 Ok, and set the AUTH_USER_NAME env variable
        # in case there is a dynamic content generator:
        # variables you want to set for the requested script/page
        # need to be prefixed w. the string 'Variable-',
        # or they will be passed back to the client, not the server.
        print(STDOUT "Status: 200 Authorized\r\n");
        print(STDOUT "Variable-AUTH_USER_NAME: $user\r\n");
        print(STDOUT "\r\n");
    }
    elsif($auth == AUTH_NOK)
    {
        # Not authorized.
        # You can make your login page the default
        # 401 page with the htaccess ErrorDocument 401 directive:
        print(STDOUT "Status: 401 Not Authorized\r\n");
        print(STDOUT "WWW-Authenticate: basic realm=\"foo\"\r\n");
        print(STDOUT "\r\n");
    }
    else
    {
        # Waah. Something blew up.
        print(STDOUT "Status: 500 Internal Auth Error\r\n");
        print(STDOUT "\r\n");
    }
}
elsif($path_auth == PATH_NEVER)
{
    # we never allow anyone in to these:
    print(STDOUT "Status: 403 Denied\r\n");
    print(STDOUT "\r\n");
}
elsif($path_auth == PATH_ALWAYS)
{
    # these we don't really care about, just let them in.
    # your error pages, icon, etc should all fall into this
    # category, as should your login page:
    print(STDOUT "Status: 200 Ok\r\n");
    print(STDOUT "\r\n");
}
else
{
    # This should not be able to happen: If it does,
    # your site needs attention from you:
    print(STDOUT "Status: 500 Internal Auth Error\r\n");
    print(STDOUT "\r\n");
}

}

_exit();

##############

Access rules, first match wins:

/auth/login.cgi is always allowed

/share/* and /icons/* are always allowed

/lib/* and /auth/* are always disallowed

anything else must be explicitly authorized

sub check_path ($)
{
my $uri = $_[0];

#warn("check_path($_[0])\n");

if ($uri =~ m@/auth/login.cgi@){ return PATH_ALWAYS }
if ($uri =~ m@/share/|/icons/@){ return PATH_ALWAYS }
if ($uri =~ m@/lib/|/auth/@)   { return PATH_NEVER  }

return PATH_MAYBE;

}

sub authorized ($$$)
{
my $rv;
my $row;
my $sth = $_[0];

my $user  = $_[1];
my $token = $_[2];

if(!$sth->execute($user,$token))
{
    warn("DBI error: ", $sth->errstr(), "\n");
    return AUTH_ERR;
}

if($row = $sth->fetchrow_arrayref())
{
    # only interested in one column in this case
    $rv = $row->[0] ? AUTH_OK : AUTH_NOK;
}
else
{
    warn("DBI error: ", $sth->errstr(), "\n");
    $rv = AUTH_ERR;
}

# paranoia: empty out the sql result buffer, just in case
# so that it's clean for the next invocation
while ($sth->{Active}) { $sth->fetchrow_arrayref() }

return $rv;

}

open a database connection and prepare the query

sub _init ()
{
$DBH = DBI->connect(DBI_DSN, DBI_USR, DBI_PWD)
|| die(“DBI->connect() failed: $DBI::errstr\n”);

$STH = $DBH->prepare(AUTH_QUERY)
  || die("prepare(AUTH_QUERY) failed: ",$DBH->errstr(),"\n");

}

clean up and close down

sub _exit ()
{
$STH->finish();
$DBH->disconnect();
}

extract a login cookie from the headers:

the assumption is made here that any… unusual characters

in the cookie have been %XX encoded:

sub get_login_cookie ()
{
my $cval = undef();

if(exists($ENV{HTTP_COOKIE}))
{
    my @cookie = split(/; /,$ENV{HTTP_COOKIE});

  COOKIE:
    foreach my $ck (@cookie)
    {
        my($n, $v) = query_decode(split(/=/,$ck,2));

        if($n eq COOKIE_NAME) { $cval = $v; last COOKIE }
    }
}

return $cval;

}

%XX decode a string or strings:

sub query_decode (;@)
{
my @str = @_;
my $item;

foreach $item (@str)
{
    $item =~ tr/+/ /;
    $item =~ s/\%([A-F\d]{2})/chr(hex($1))/gei;
}

return wantarray ? (@str) : $str[0];

}

END

/* mysql login table create statement */
create table login_table (token char(32) not null,
user char(32) not null,
expiry int(11) not null)

2009/1/12 Igor S. [email protected]:

lhmwzy wrote:

You can look at the way how lighttpd implements FastCGI Authorizers.

2009/1/12 Igor C. [email protected]:

Thanks for the reply. I was thinking about how to do it in terms of
nginx module architecture?

Cheers
IC

lhmwzy wrote:

I don’t know c and can’t help you to program.
But I look forward to hearing your success.

Thanks, it’s something I’m interested in doing (time permitting,
obviously) but as my C is rusty and my experience with nginx’s module
interface limited, I would like to validate an approach before embarking
on it.

cheers
IC

On Mon, Jan 12, 2009 at 01:28:47PM +0100, Igor C. wrote:

lhmwzy wrote:

You can look at the way how lighttpd implements FastCGI Authorizers.

2009/1/12 Igor C. [email protected]:

Thanks for the reply. I was thinking about how to do it in terms of
nginx module architecture?

nginx has special access phase where ngx_http_access_module and
ngx_http_auth_basic_module work. However, implementing FastCGI auth is
not
too easy task.