RE: POST+Upgrade, or unexpected limitation of RFC8441

I think the overload is the fundamental issue here. In HTTP/1.1, there's an HTTP request which optionally allows the server to answer the request over a different protocol if that's more convenient. That's why, in h2c, stream 1 was assigned to the client's request, started half-closed, and the server responded to the request on that stream. The request isn't gone, it's answered over the new protocol. If the new protocol isn't supported by the server, then the request is answered successfully on the existing protocol (HTTP/1.1).

However, with extended CONNECT, there is no request being made independent of the protocol change -- it's purely a request for a protocol, albeit with an endpoint located at a particular URL. If the desired protocol isn't supported, the request will fail. If it succeeds, there's no pending request to be answered in the new protocol.

Those are similar, but subtly different mechanisms. It's also why you can't Extended CONNECT to h2c without further definition.

> -----Original Message-----
> From: Glenn Strauss <gs-lists-ietf-http-wg@gluelogic.com>
> Sent: Wednesday, December 18, 2024 6:57 AM
> To: Willy Tarreau <w@1wt.eu>
> Cc: HTTP Working Group <ietf-http-wg@w3.org>; mcmanus@ducksong.com
> Subject: Re: POST+Upgrade, or unexpected limitation of RFC8441
> 
> On Wed, Dec 18, 2024 at 11:28:34AM +0100, Willy Tarreau wrote:
> > Hi!
> >
> > RFC8441 ("Bootstrapping WebSockets with HTTP/2") indicates how to deal
> > with an Upgrade over HTTP/2. To make it short, HTTP/1's
> > Connection+Upgrade->101 becomes a CONNECT+:protocol->200 in H2.
> > The RFC specifies a :protocol pseudo-header that allows to use the
> > mechanism beyond Websocket.
> >
> > A while ago, we've implemented the mechanism in haproxy and we support
> > the upgrade from H1+Upgrade to H2+CONNECT. So far it works fine for
> > our users.
> >
> > A few months ago we got a report of breakage[1] that unveiled
> > something unexpected: given that CONNECT doesn't support a request
> > body, we had simplified our support for Upgrade and completely
> > rejected any request body at the H1 layer (so that we could fail early
> > when not knowing if the output would be in
> > H1 or H2).  But doing so we accidentally broke support for the "Docker
> > Engine API" protocol over H1. That's how we discovered that some
> > protocols were relying on POST + Upgrade! (maybe it's the only one, I
> > don't know, it was the first report in a few years of existence).
> 
> HTTP/1.1 Upgrade overloads the request.
> 
> There is an HTTP 1.1 request and there is an HTTP/1.1 Upgrade request.
> 
> If the Upgrade is not handled, the HTTP request proceeds.
> 
> If the Upgrade is to be handled, then the request be must read in full prior to the
> Upgrade taking effect on data *after the full request* received from the client.
> There is a danger here for exposure to request smuggling if various intermediaries
> handle or do not handle the request body differently.
> 
> Some of these issues were discussed earlier this year regarding Security
> Considerations for Optimistic Use of HTTP Upgrade
> https://datatracker.ietf.org/doc/draft-schwartz-httpbis-optimistic-upgrade/
> both on list and at the IETF meetings, e.g.
> https://httpwg.org/wg-materials/ietf120/minutes.html#security-considerations-
> for-optimistic-use-of-http-upgrade----ben-schwartz
> 
> (I think on the list) I made a suggestion that the draft limit future Upgrade tokens
> for use in requests *without* a request body and with idempotent HTTP request
> methods.
> 
> I still feel that way and so for these reasons would oppose adding complexity to
> extend RFC8441 CONNECT to allow a request body.
> 
> Why does the docker engine API use POST along with Upgrade?
> HTTP POST method is not intrinsically idempotent.
> Would it not be better if the docker API sent the POST request, and separately,
> depending on success/failure of the POST request, sent an HTTP/1.1 Upgrade:
> websocket request via GET?
> 
> From my quick look in Moby, the open source repo for community Docker:
> 
> Search for "Upgrade:" to see the examples
> Upgrade: tcp and Upgrade: h2c
> https://github.com/moby/moby/blob/master/api/swagger.yaml
> 
> Since swagger.yaml mentions Upgrade: h2c, I'll note that lighttpd will ignore
> HTTP/1.1 Upgrade: h2c if there is a non-zero HTTP request body included with
> HTTP/1.1 Upgrade: h2c.  That is done to avoid the potential for request smuggling
> with HTTP/1.1 Upgrade: h2c.
> 
> At the moment, I do not know why Docker API is using POST instead of GET and if
> the POST even has a body, or if it happens to use
> Transfer-Encoding: chunked even in cases where it could explicitly use
> Content-Length: 0.
> 
> >   [...] but the problem remains that the mechanism proposed by
> > RFC8441 doesn't offer provisions for completely transporting H1 over
> > H2, nor replacing H1 with H2. [...]
> 
> True.  Perhaps HTTP/1.1 RFCs should be updated to deprecate non-zero request
> body and use an idempotent method when including an Upgrade token with the
> request?  Similar to your last suggestion:
> 
> >   - or we could do nothing and consider that some parts of HTTP/1 will
> >     remain HTTP/1 forever and will not be transportable over newer
> >     versions. I'm not fundamentally against it but it would warrant
> >     some extra documentation (especially in the H1 related specs) to
> >     discourage such use so that we make sure no new protocol adopts
> >     them and stay stuck in a corner.
> 
> BTW, there is already another outstanding issue mentioned on this list (but not
> followed with an errata) about HTTP/2 extended CONNECT breaking HTTP Digest
> authentication for an HTTP/1.x request translated to HTTP/2 since the HTTP
> method is included in the digest, but the HTTP/1.1 request sent over HTTP/2 uses
> HTTP method CONNECT. [1]  Ben noted that the issue may also apply to MASQUE
> protocols.
> 
> [1] https://lists.w3.org/Archives/Public/ietf-http-wg/2024JulSep/0169.html
> 
> Cheers, Glenn

Received on Wednesday, 8 January 2025 17:02:01 UTC