Document that request.get_host() fails when behind multiple reverse proxies
|Reported by:||tomevans222||Owned by:||nobody|
|Severity:||Normal||Keywords:||proxy forwarded get_host|
|Has patch:||yes||Needs documentation:||no|
|Needs tests:||no||Patch needs improvement:||no|
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.
2) Change request.get_host() to look for multi value versions of X-Forwarded-Host and pull out the correct value.
3) Add separate middleware to rewrite request.META with just the 'closest' version of these headers.
... 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.
Change History (8)
Changed 6 years ago by tomevans222
comment:1 Changed 6 years ago by jacob
- Component changed from Core framework to Documentation
- Needs documentation unset
- Needs tests unset
- Patch needs improvement unset
- Summary changed from request.get_host() fails when behind multiple reverse proxies to Document that request.get_host() fails when behind multiple reverse proxies
- Triage Stage changed from Unreviewed to Accepted
Changed 5 years ago by arnav
comment:4 Changed 4 years ago by gabrielhurley
- Resolution set to fixed
- Status changed from new to closed