Re: WSGI 2.0 Round 2: requirements and call for interest

Graham Dumpleton <[email protected]> Tue, 5 Jan 2016 22:57:57 +1100
Newsgroups gmane.comp.python.web
Message-ID <[email protected]>
--===============0738136844962119120==
Content-Type: multipart/alternative; boundary="Apple-Mail=_16C39E78-34D9-4200-A3E9-BAFE49125B1A"


--Apple-Mail=_16C39E78-34D9-4200-A3E9-BAFE49125B1A
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
	charset=utf-8


> On 5 Jan 2016, at 10:26 PM, Cory Benfield <[email protected]> wrote:
>=20
> Forwarding this message from the django-developers list.
>=20
> Hi Cory,
>=20
> I=E2=80=99m not subscribed to web-sig but I read the discussion there. =
Feel free to forward my answer to the group if you think it=E2=80=99s =
useful.
>=20
> I have roughly the same convictions as Graham Dumpleton. If you want =
to support HTTP/2 and WebSockets, don=E2=80=99t start with design =
decisions anchored in CGI. Figure out what a simple and flexible API for =
these new protocols would be, specify it, implement it, and make sure it =
degrades gracefully to HTTP/1. You may be able to channel most of the =
communication through a single generator, but it=E2=80=99s unclear to me =
that this will be the most convenient design.
>=20
> If you want to improve WSGI, here=E2=80=99s a list of mistakes or =
shortcomings in PEP 3333 that you can take a stab at. There=E2=80=99s a =
general theme: for a specification that looks at the future, I believe =
that making modern PaaS-based deployments secure by default matters more =
than not implementing anything beyond what=E2=80=99s available in legacy =
CGI-based deployments.
>=20
> 1. WSGI is prone to header injection vulnerabilities issues by design =
due to the conversion of HTTP headers to CGI-style environment =
variables: if the server doesn=E2=80=99t specifically prevent it, X-Foo =
and X_Foo both become HTTP_X_Foo. I don=E2=80=99t believe it=E2=80=99s a =
good choice to destructively encode headers, expect applications to undo =
the damage somehow, and introduce security vulnerabilities in the =
process. If mimicking CGI is still considered a must-have =E2=80=94 1% =
of current Python web programmers may have heard about it, most of them =
from PEP 3333 =E2=80=94 then that burden should be pushed onto the =
server, not the application.

FWIW, Apache 2.4 will discard headers which would use underscore, as =
well as many other characters. Basically it probably only accepts =
alphanumeric and =E2=80=98-=E2=80=98 in original name.

In mod_wsgi, it does the same thing, even for Apache 2.2 where it =
wasn=E2=80=99t done.

So with mod_wsgi at least you are safe. Or at least if not still using =
some ancient mod_wsgi version. (Death to LTS Linux versions and out of =
date packages) :-)

The nginx server if used as a front end and where it is populating CGI =
like variables for passing to a builtin module such as uWSGI will also I =
believe discard headers which don=E2=80=99t match that requirement as =
well.

I can=E2=80=99t remember if gunicorn was updated to do something =
similar, or whether when uWSGI isn=E2=80=99t used behind nginx via its =
uwsgi protocol, but instead listens publicly via HTTP whether it does it =
either.=20

> 2. More generally, I fail to see how mixing HTTP headers, =
server-related inputs, and environment variables in a dict adds values. =
It prevents iterating on each collection separately. It only makes sense =
if not offering more features than CGI is a design goal; in that case, =
this discussion doesn=E2=80=99t serve a purpose anyway. It would be =
nicer and possibly more secure if the application received separately:
>=20
> a. Configuration information, which servers could read from =
environment variables by default for backwards compatibility, but could =
also get through more secure channels and restrict to what the =
application needs in order to better isolate it from the entire OS.

I have always had a bit of a beef with the way that the use of =
environment variables for configuration was promoted by the 12 factor =
manifesto. It grew out of how a specific hosting service did things and =
ignored that various web servers used configuration files instead or did =
things in other ways. Of course the hosting service made it difficult to =
impossible to use some of those traditional web servers, so they were =
safe in their narrow view of things.

Anyway, if environment variables were used where appropriate and with an =
intermediate mapping layer within Python web applications that would =
have been fine. The problem was that you started to see direct lookup of =
environment variables deep in code bases. So people wedded themselves to =
use of environment variables.

The more sensible thing to do would have been to use an intermediate =
Python module/package providing an abstraction layer for getting =
configuration. Code would then use that. The configuration layer could =
then look up environment variables or use other means to get =
configuration, such as from more traditional configuration files, or =
pulling it done from configuration servers.

As far as I know there is no good Python package out there which serves =
as such a intermediary configuration system which could be plugged into =
any application and which doesn=E2=80=99t carry a huge amount of =
baggage. Would love to hear about one if it exists.

> b. Server APIs mandated by the spec, per request.
> c. HTTP headers, per request.
>=20
> 3. Stop pretending that HTTP is a unicode protocol, or at least stop =
ignoring reality when doing so. WSGI enforces ISO-8859-1-decoded str =
objects in the environ, which is just wrong. It=E2=80=99s all the more a =
surprising choice since this change was driven by Python 3, that UTF-8 =
is the correct choice, and that Python 3 defaults to UTF-8. Django has =
to re-encode and re-decode before doing anything with HTTP headers: =
https://github.com/django/django/blob/d5b90c8e120687863c1d41cf92a4cdb11413=
ad7f/django/core/handlers/wsgi.py#L231-L253 =
<https://github.com/django/django/blob/d5b90c8e120687863c1d41cf92a4cdb1141=
3ad7f/django/core/handlers/wsgi.py#L231-L253>

WSGI uses ISO-8859-1 with raw bytes because there is no guarantee about =
what the encoding might be for certain inbound headers due to clients =
ignoring the ASCII requirement and using what they please. A header =
value may well be UTF-8 these days, but technically it could also be =
some obscure Japanese encoding.

Ultimately there was no way the WSGI server could know for sure what the =
encoding of some header values could be. The only place that knew this =
for sure was the web application itself.

This is why it ended up as ISO-8859-1 encoded raw bytes.

There would be quite a lot of discussion about this topic back in the =
Web-SIG archives somewhere.

> 4. Normalize the way to tell the application about the original =
protocol, IP address and port. When dev and ops responsibilities are =
separate, this is clearly an ops responsibility, but due to the lack of =
standardization devs end up dealing with this problem in custom =
middleware, when they do it at all. Everyone keeps getting it wrong, =
which introduces security vulnerabilities. Also it always breaks =
silently on infrastructure changes.

FWIW, I came to the conclusion that it is the responsibility of the WSGI =
server to translate any proxy forwarding headers. As you say, if you =
leave it up to WSGI applications or middleware it becomes a mess and is =
usually wrong.

If you dig into the mod_wsgi code I have all these capabilities now =
where you can tell mod_wsgi what are the trusted proxy headers from your =
proxy and also what are trusted proxy IP addresses or address ranges. =
With that mod_wsgi will then update various stuff that goes into the =
WSGI environ and deletes all the headers which weren=E2=80=99t marked as =
trusted so that WSGI middleware or applications then don=E2=80=99t =
incorrectly use them.

I talked a bit about this in a blog post:

    =
http://blog.dscpl.com.au/2015/06/proxying-to-python-web-application.html =
<http://blog.dscpl.com.au/2015/06/proxying-to-python-web-application.html>=


Unfortunately I haven=E2=80=99t updated it to support the new =
=E2=80=98Forwarded=E2=80=99 header yet, which I should do.

Common to a common understanding of what should be done in this space =
and have all WSGI servers provide the same functionality would be nice.

> 5. Improve request / response length handling and connection closure. =
Armin and Graham have talked about in the past and know the topic better =
than I do. There=E2=80=99s also a rejected PEP by Armin which made sense =
to me.
>=20
> As you can see from these comments, I don=E2=80=99t quite share the =
design choices that led to WSGI as it currently stands. I think it will =
be easier to build a new standard than evolve the current one.
>=20
> I hope this helps!
>=20
> Aymeric
> _______________________________________________
> Web-SIG mailing list
> [email protected]
> Web SIG: http://www.python.org/sigs/web-sig
> Unsubscribe: =
https://mail.python.org/mailman/options/web-sig/graham.dumpleton%40gmail.c=
om


--Apple-Mail=_16C39E78-34D9-4200-A3E9-BAFE49125B1A
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html;
	charset=utf-8

<html><head><meta http-equiv=3D"Content-Type" content=3D"text/html =
charset=3Dutf-8"></head><body style=3D"word-wrap: break-word; =
-webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" =
class=3D""><br class=3D""><div><blockquote type=3D"cite" class=3D""><div =
class=3D"">On 5 Jan 2016, at 10:26 PM, Cory Benfield &lt;<a =
href=3D"mailto:[email protected]" class=3D"">[email protected]</a>&gt; =
wrote:</div><br class=3D"Apple-interchange-newline"><div class=3D""><div =
class=3D"">Forwarding this message from the django-developers list.<br =
class=3D""><br class=3D"">Hi Cory,<br class=3D""><br class=3D"">I=E2=80=99=
m not subscribed to web-sig but I read the discussion there. Feel free =
to forward my answer to the group if you think it=E2=80=99s useful.<br =
class=3D""><br class=3D"">I have roughly the same convictions as Graham =
Dumpleton. If you want to support HTTP/2 and WebSockets, don=E2=80=99t =
start with design decisions anchored in CGI. Figure out what a simple =
and flexible API for these new protocols would be, specify it, implement =
it, and make sure it degrades gracefully to HTTP/1. You may be able to =
channel most of the communication through a single generator, but it=E2=80=
=99s unclear to me that this will be the most convenient design.<br =
class=3D""><br class=3D"">If you want to improve WSGI, here=E2=80=99s a =
list of mistakes or shortcomings in PEP 3333 that you can take a stab =
at. There=E2=80=99s a general theme: for a specification that looks at =
the future, I believe that making modern PaaS-based deployments secure =
by default matters more than not implementing anything beyond what=E2=80=99=
s available in legacy CGI-based deployments.<br class=3D""><br =
class=3D"">1. WSGI is prone to header injection vulnerabilities issues =
by design due to the conversion of HTTP headers to CGI-style environment =
variables: if the server doesn=E2=80=99t specifically prevent it, X-Foo =
and X_Foo both become HTTP_X_Foo. I don=E2=80=99t believe it=E2=80=99s a =
good choice to destructively encode headers, expect applications to undo =
the damage somehow, and introduce security vulnerabilities in the =
process. If mimicking CGI is still considered a must-have =E2=80=94 1% =
of current Python web programmers may have heard about it, most of them =
from PEP 3333 =E2=80=94 then that burden should be pushed onto the =
server, not the application.<br =
class=3D""></div></div></blockquote><div><br class=3D""></div><div>FWIW, =
Apache 2.4 will discard headers which would use underscore, as well as =
many other characters. Basically it probably only accepts alphanumeric =
and =E2=80=98-=E2=80=98 in original name.</div><div><br =
class=3D""></div><div>In mod_wsgi, it does the same thing, even for =
Apache 2.2 where it wasn=E2=80=99t done.</div><div><br =
class=3D""></div><div>So with mod_wsgi at least you are safe. Or at =
least if not still using some ancient mod_wsgi version. (Death to LTS =
Linux versions and out of date packages) :-)</div><div><br =
class=3D""></div><div>The nginx server if used as a front end and where =
it is populating CGI like variables for passing to a builtin module such =
as uWSGI will also I believe discard headers which don=E2=80=99t match =
that requirement as well.</div><div><br class=3D""></div><div>I can=E2=80=99=
t remember if gunicorn was updated to do something similar, or whether =
when uWSGI isn=E2=80=99t used behind nginx via its uwsgi protocol, but =
instead listens publicly via HTTP whether it does it =
either.&nbsp;</div><br class=3D""><blockquote type=3D"cite" =
class=3D""><div class=3D""><div class=3D"">2. More generally, I fail to =
see how mixing HTTP headers, server-related inputs, and environment =
variables in a dict adds values. It prevents iterating on each =
collection separately. It only makes sense if not offering more features =
than CGI is a design goal; in that case, this discussion doesn=E2=80=99t =
serve a purpose anyway. It would be nicer and possibly more secure if =
the application received separately:<br class=3D""><br class=3D"">a. =
Configuration information, which servers could read from environment =
variables by default for backwards compatibility, but could also get =
through more secure channels and restrict to what the application needs =
in order to better isolate it from the entire OS.<br =
class=3D""></div></div></blockquote><div><br class=3D""></div><div>I =
have always had a bit of a beef with the way that the use of environment =
variables for configuration was promoted by the 12 factor manifesto. It =
grew out of how a specific hosting service did things and ignored that =
various web servers used configuration files instead or did things in =
other ways. Of course the hosting service made it difficult to =
impossible to use some of those traditional web servers, so they were =
safe in their narrow view of things.</div><div><br =
class=3D""></div><div>Anyway, if environment variables were used where =
appropriate and with an intermediate mapping layer within Python web =
applications that would have been fine. The problem was that you started =
to see direct lookup of environment variables deep in code bases. So =
people wedded themselves to use of environment variables.</div><div><br =
class=3D""></div><div>The more sensible thing to do would have been to =
use an intermediate Python module/package providing an abstraction layer =
for getting configuration. Code would then use that. The configuration =
layer could then look up environment variables or use other means to get =
configuration, such as from more traditional configuration files, or =
pulling it done from configuration servers.</div><div><br =
class=3D""></div><div>As far as I know there is no good Python package =
out there which serves as such a intermediary configuration system which =
could be plugged into any application and which doesn=E2=80=99t carry a =
huge amount of baggage. Would love to hear about one if it =
exists.</div><br class=3D""><blockquote type=3D"cite" class=3D""><div =
class=3D""><div class=3D"">b. Server APIs mandated by the spec, per =
request.<br class=3D"">c. HTTP headers, per request.<br class=3D""><br =
class=3D"">3. Stop pretending that HTTP is a unicode protocol, or at =
least stop ignoring reality when doing so. WSGI enforces =
ISO-8859-1-decoded str objects in the environ, which is just wrong. =
It=E2=80=99s all the more a surprising choice since this change was =
driven by Python 3, that UTF-8 is the correct choice, and that Python 3 =
defaults to UTF-8. Django has to re-encode and re-decode before doing =
anything with HTTP headers: <a =
href=3D"https://github.com/django/django/blob/d5b90c8e120687863c1d41cf92a4=
cdb11413ad7f/django/core/handlers/wsgi.py#L231-L253" =
class=3D"">https://github.com/django/django/blob/d5b90c8e120687863c1d41cf9=
2a4cdb11413ad7f/django/core/handlers/wsgi.py#L231-L253</a><br =
class=3D""></div></div></blockquote><div><br class=3D""></div><div>WSGI =
uses ISO-8859-1 with raw bytes because there is no guarantee about what =
the encoding might be for certain inbound headers due to clients =
ignoring the ASCII requirement and using what they please. A header =
value may well be UTF-8 these days, but technically it could also be =
some obscure Japanese encoding.</div><div><br =
class=3D""></div><div>Ultimately there was no way the WSGI server could =
know for sure what the encoding of some header values could be. The only =
place that knew this for sure was the web application =
itself.</div><div><br class=3D""></div><div>This is why it ended up as =
ISO-8859-1 encoded raw bytes.</div><div><br class=3D""></div><div>There =
would be quite a lot of discussion about this topic back in the Web-SIG =
archives somewhere.</div><br class=3D""><blockquote type=3D"cite" =
class=3D""><div class=3D""><div class=3D"">4. Normalize the way to tell =
the application about the original protocol, IP address and port. When =
dev and ops responsibilities are separate, this is clearly an ops =
responsibility, but due to the lack of standardization devs end up =
dealing with this problem in custom middleware, when they do it at all. =
Everyone keeps getting it wrong, which introduces security =
vulnerabilities. Also it always breaks silently on infrastructure =
changes.<br class=3D""></div></div></blockquote><div><br =
class=3D""></div><div>FWIW, I came to the conclusion that it is the =
responsibility of the WSGI server to translate any proxy forwarding =
headers. As you say, if you leave it up to WSGI applications or =
middleware it becomes a mess and is usually wrong.</div><div><br =
class=3D""></div><div>If you dig into the mod_wsgi code I have all these =
capabilities now where you can tell mod_wsgi what are the trusted proxy =
headers from your proxy and also what are trusted proxy IP addresses or =
address ranges. With that mod_wsgi will then update various stuff that =
goes into the WSGI environ and deletes all the headers which weren=E2=80=99=
t marked as trusted so that WSGI middleware or applications then don=E2=80=
=99t incorrectly use them.</div><div><br class=3D""></div><div>I talked =
a bit about this in a blog post:</div><div><br =
class=3D""></div><div>&nbsp; &nbsp;&nbsp;<a =
href=3D"http://blog.dscpl.com.au/2015/06/proxying-to-python-web-applicatio=
n.html" =
class=3D"">http://blog.dscpl.com.au/2015/06/proxying-to-python-web-applica=
tion.html</a></div><div><br class=3D""></div><div>Unfortunately I =
haven=E2=80=99t updated it to support the new =E2=80=98Forwarded=E2=80=99 =
header yet, which I should do.</div><div><br class=3D""></div><div>Common =
to a common understanding of what should be done in this space and have =
all WSGI servers provide the same functionality would be nice.</div><br =
class=3D""><blockquote type=3D"cite" class=3D""><div class=3D""><div =
class=3D"">5. Improve request / response length handling and connection =
closure. Armin and Graham have talked about in the past and know the =
topic better than I do. There=E2=80=99s also a rejected PEP by Armin =
which made sense to me.<br class=3D""><br class=3D"">As you can see from =
these comments, I don=E2=80=99t quite share the design choices that led =
to WSGI as it currently stands. I think it will be easier to build a new =
standard than evolve the current one.<br class=3D""><br class=3D"">I =
hope this helps!<br class=3D""><br class=3D"">Aymeric<br =
class=3D"">_______________________________________________<br =
class=3D"">Web-SIG mailing list<br class=3D""><a =
href=3D"mailto:[email protected]" class=3D"">[email protected]</a><br =
class=3D"">Web SIG: http://www.python.org/sigs/web-sig<br =
class=3D"">Unsubscribe: =
https://mail.python.org/mailman/options/web-sig/graham.dumpleton%40gmail.c=
om<br class=3D""></div></div></blockquote></div><br =
class=3D""></body></html>=

--Apple-Mail=_16C39E78-34D9-4200-A3E9-BAFE49125B1A--

--===============0738136844962119120==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Web-SIG mailing list
[email protected]
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: https://mail.python.org/mailman/options/web-sig/gcpw-web-sig%40m.gmane.org

--===============0738136844962119120==--