﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
11877	Document that request.get_host() fails when behind multiple reverse proxies	tomevans222	nobody	"We run django behind several layers of proxies (please excuse ASCII art):
{{{
    Client
       |
       |
Apache (handling SSL connections, mod_proxy)
       |
       |
Apache (handling static content, load balancing, mod_proxy, mod_proxy_balancer)
    |                           | 
    |                           |
Apache (mod_fastcgi)       Apache (mod_fastcgi)
    |                           |
    |                           |
django instance             django instance
}}}
Each apache instance that proxies a request appends the ServerName of the proxy server into the X-Forwarded-For and X-Forwarded-Host headers. In our infrastructure, all of the apache instances respond to the same host name (which is preserved through the proxying using ProxyPreserveHost directive), so it ends up looking like this in request.META:
{{{
'HTTP_X_FORWARDED_FOR: '10.0.0.1, 10.0.0.2'
'HTTP_X_FORWARDED_HOST': 'portal.example.com, portal.example.com'
'HTTP_X_FORWARDED_SERVER': 'portal.example.com, portal.example.com'
}}}

This then breaks request.get_host(), which does the following:

{{{
   def get_host(self):
        """"""Returns the HTTP host using the environment or request headers.""""""
        # We try three options, in order of decreasing preference.
        if 'HTTP_X_FORWARDED_HOST' in self.META:
            host = self.META['HTTP_X_FORWARDED_HOST']
        elif 'HTTP_HOST' in self.META:
            host = self.META['HTTP_HOST']
        else:
            # Reconstruct the host using the algorithm from PEP 333.
            host = self.META['SERVER_NAME']
            server_port = str(self.META['SERVER_PORT'])
            if server_port != (self.is_secure() and '443' or '80'):
                host = '%s:%s' % (host, server_port)
        return host
}}}

IE, it completely ignores the possibility of more than one host in X-Forwarded-Host.

Breaking request.get_host() breaks request.build_absolute_uri(), which in turn breaks HttpResponseRedirect*, leading to redirects with location headers like:
{{{
  Location: http://portal.example.com, portal.example.com/foo/
}}}
I've not attached a patch, because I can think of a number of ways this could be fixed:

1) Change request parsing to notice multi value headers and correctly parse into arrays.[[BR]]
2) Change request.get_host() to look for multi value versions of X-Forwarded-Host and pull out the correct value.[[BR]]
3) Add separate middleware to rewrite request.META with just the 'closest' version of these headers.[[BR]]
... any number of other solutions.

This obviously is not a 'normal' deployment of django... We will probably work around with custom middleware for now, it is the least intrusive.

This is with 1.0, but the code for request.get_host() is the same in trunk."	Uncategorized	closed	Documentation	1.0	Normal	fixed	proxy forwarded get_host		Accepted	1	0	0	0	0	0
