Opened 3 years ago

Closed 3 years ago

#33569 closed Cleanup/optimization (fixed)

Add support for multiple values for the x-forwarded-proto header

Reported by: Thomas Schmidt Owned by: Thomas Schmidt
Component: HTTP handling Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When Django is deployed behind more than one proxy, the proxy behavior is sometimes to list the protocol as a comma-separated list.

However, currently, Django expects only one value for the x-forwarded-proto header, instead of parsing it as a list of values and setting the protocol accordingly.

x-forwarded-proto is a non-standard header, so there isn't a specification for its use, but different reverse-proxy vendors do use it in different ways, and some append the protocol as a comma-separated value from left-to-right (left being the furthermost proxy and rightmost being the closest).

Similar issues have been raised and implemented in other projects, for example:

Tornado:

Ruby:

Reactor-Netty:

Most implementation use the leftmost-value or rightmost value. I would expect that provided that you are certain that the initial proxy can be trusted, that the left-most value makes the most sense, since it represent the original value at the entry-point for the HTTP request which is often where TLS is being terminated.

Common example of this behavior is when using mulitple AWS proxies such as API Gateway proxying to an elastic load balancer.

Change History (11)

comment:1 by Thomas Schmidt, 3 years ago

Has patch: set

comment:2 by Claude Paroz, 3 years ago

comment:3 by Mariusz Felisiak, 3 years ago

Owner: changed from nobody to Thomas Schmidt
Patch needs improvement: set
Status: newassigned

comment:4 by Mariusz Felisiak, 3 years ago

Resolution: wontfix
Status: assignedclosed

As far as I'm aware, we cannot answer in the reliable way whether the request was over HTTPS, when X-Forwarded-Proto contains multiple values. Unfortunately it depends on which layer is trusted. We could check if all values are "https", but this could be unacceptable with in-companies proxies. It's definitely not enough to check if "https" is used somewhere in the pipeline.

As a workaround you can set list of protocols in the SECURE_PROXY_SSL_HEADER, e.g. SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https,http"). I don't think it's worth additional complexity. You can start a discussion on DevelopersMailingList if you don't agree.

comment:5 by Florian Apolloner, 3 years ago

As far as I'm aware, we cannot answer in the reliable way whether the request was over HTTPS, when X-Forwarded-Proto contains multiple values. Unfortunately it depends on which layer is trusted. We could check if all values are "https", but this could be unacceptable with in-companies proxies. It's definitely not enough to check if "https" is used somewhere in the pipeline.

I do not think this is correct. First of all we have to make one decision -- namely do we want to trust X-Forwarded-Proto at all. By setting SECURE_PROXY_SSL_HEADER we do explicitly say that we trust the value to be correct (see the big warning in https://docs.djangoproject.com/en/4.0/ref/settings/).

Once we trust that header it is not required to check whether all items in the list are https but solely whether the left one is https. Whether the middle-items are http is irrelevant. Why? The goal of this header is to check if the client connection to the first proxy was HTTPS, nothing more nothing less. It is not an indicator on whether the connection is secure but solely an indicator whether generated links etc should have https:// or http://.

For example even if the header is set to the simple value of https there is no gurantee that the connection from the proxy to the backend was https (and more often than not it is simply http).

As a workaround you can set list of protocols in the SECURE_PROXY_SSL_HEADER, e.g. SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https,http").

I am not sure I would recommend that, this is highly implementation dependent and assumes that the proxy doesn't start sneaking in a space at some point. That said the worst case scenario here is then that the request is determined as unsafe which is probably not the end of the world… On the other hand this assumes that you actually know the number of proxies involved as opposed to just knowing that you can trust the header because the proxies to set it properly. Which might be hard in cloud environments.

I don't think it's worth additional complexity.

I would argue that the added complexity is relatively small; the biggest task here is to update the docs to explain it nicely.

comment:6 by Mariusz Felisiak, 3 years ago

Has patch: unset
Patch needs improvement: unset
Resolution: wontfix
Status: closednew
Triage Stage: UnreviewedAccepted

OK, we can prefer leftmost value, if that's enough. SECURE_PROXY_SSL_HEADER and HttpRequest.is_secure() docs must be updated.

comment:7 by Carlton Gibson, 3 years ago

Hmmm. I think adding complexity here (at all) will come back and bite us.

Which might be hard in cloud environments.

The issue is that there's no easy "I only use HTTPS switch", which is most deployments these days. That makes folks rely on ​SECURE_PROXY_SSL_HEADER, which gets ever more tricky as deployments get ever more baroque. Such a switch would solve most use-cases, I submit :)

Short of that, the lightest WSGI middleware setting wsgi.url_scheme in the environ would be preferable to getting into parsing SECURE_PROXY_SSL_HEADER (for me).

comment:8 by Thomas Schmidt, 3 years ago

I can re-open the closed PR and update it to take the comments made here into account, so we can discuss it.

Summary:

Version 0, edited 3 years ago by Thomas Schmidt (next)

comment:9 by Mariusz Felisiak, 3 years ago

Triage Stage: AcceptedReady for checkin

comment:10 by Mariusz Felisiak, 3 years ago

Has patch: set
Status: newassigned

comment:11 by Mariusz Felisiak <felisiak.mariusz@…>, 3 years ago

Resolution: fixed
Status: assignedclosed

In 1cf60ce:

Fixed #33569 -- Added SECURE_PROXY_SSL_HEADER support for list of protocols in the header value.

Note: See TracTickets for help on using tickets.
Back to Top