Setting REMOTE_USER for Trac in Gunicorn behind Nginx

I want to run Trac in Gunicorn, behind Nginx. Nginx handles user authentication via LDAP (which works), but I can't get the REMOTE_USER passed to Trac.

For uWSGI I would configure Nginx like this (tested and it works):

uwsgi_param REMOTE_USER $remote_user;

For Gunicorn I could not find a similar configuration directive. I have tried setting the header, but it doesn't seem to change anything:

proxy_set_header REMOTE_USER $remote_user;

There is an entry in the Trac wiki about this, noting that "the wsgi entry point does not handle [...] authentication", and a workaround to handle Basic Authentication with a local password file, which is not what I need. Do I need to change my entry point to pass this REMOTE_USER header to the right environment variable? Currently it's just this:

import sys
import os

sys.stdout = sys.stderr

os.environ['TRAC_ENV'] = '/path/to/my/trac'

import trac.web.main
application = trac.web.main.dispatch_request

Strange one. I got this working by including the this in the nginx config like you mentioned

proxy_set_header REMOTE_USER $remote_user;

Then for the trac.wsgi file this

import trac.web.main
def application(environ, start_application):
    environ['REMOTE_USER'] = environ.get('HTTP_REMOTE_USER')
    return trac.web.main.dispatch_request(environ, start_application)

replaces this

import trac.web.main
application = trac.web.main.dispatch_request

in your trac.wsgi

Apparently there is something about the proceeding 'HTTP_' that messes up trac's auth

I am guessing that the same thing probably could be accomplished by doing the same thing to just the 'HTTP_AUTHORIZATION' request header but I didn't attempt that. So I do not know for sure, all I know is that it works now!


I had a similar problem with Mercurial; byoungb's answer, above, is OK in principle but should be using X-Remote-User, not REMOTE_USER in the proxy_set_header, and then HTTP_X_REMOTE_USER in the environ.get() call.

However, since most people use SSL and then do Basic authentication, you can use the Authorization header (which arrives in your environment as HTTP_AUTHORIZATION) as follows:

import base64, re

def use_basic_auth(application):
    def auth_app(environ, start_response):
        auth = environ.get('HTTP_AUTHORIZATION')
        if auth:
            scheme, creds = re.split(r'\s+', auth)
            if scheme.lower() != 'basic':
                raise ValueError('Unknown auth scheme \"%s\"' % scheme)
            user, pword = base64.b64decode(creds).split(':', 1)
            environ['REMOTE_USER'] = user

        return application(environ, start_response)
    return auth_app

Then you can write (for Trac)

application = use_basic_auth(trac.web.main.dispatch_request)

or, in my case (for Mercurial)

application = use_basic_auth(hgweb(config))

This obviously won’t work if you’re using something other than HTTP Basic authentication. In that case, you could either do what byoungb suggests (albeit using a better header name), or you could move the authentication to the Python end of things rather than getting nginx to do it. The downside of the latter is that you might be more vulnerable if there's some security hole somewhere.