You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
First question Id have here is: does this reproduce if not setting a particular proxy mode?
The default behavior is to tunnel HTTPS requests, so given that you're requesting an HTTPS resource (ifconfig.me) I would expect the issue to remain if you just dropped the mode option and just passed:
proxies="https://..."
If that's the case then we can look at the dance happening when using an HTTPS proxy URL.
Also, are you able to make requests through this HTTPS proxy using another client, such as cURL or Requests?
Okay, so from the traceback you should, it fails during .start_tls(), which is the point where we wrap the TCP socket that was open by the proxy during the initial CONNECT request, and do the TLS handshake on it.
In your case, the proxy URL is an HTTPS one, so we do the CONNECT request via HTTPS, which means the TCP socket we get is already wrapped around TLS. So, it shouldn't be much of a surprise that doing a TLS handshake with the remote server on a connection that's already encrypted without the remote knowing it is bound to lead to… problems. :-) At least our .start_tls()-based approach to tunneling is obviously too naive to handle this case.
Incidently, we don't actually have a proper test for + in HTTPCore (only HTTP/HTTP and HTTP/HTTPS).
We use pproxy there, so one way to possibly reproducing this would be to setup pproxy locally with TLS certs, and then issuing a request to an HTTPS resource through that.
@ElliottMoos Are you sure the Requests example gets any traffic routed to the HTTPS proxy? I tried your snippet and I can actually put anything as the https proxy and the request will work, so…?
As for curl, I can confirm it works well for ifconfig.me. I forced --http1.1 so we're not bothered with HTTP/2 dances, and passed -v so we can see the full list of events happening:
But as curl shows us, those two should be decoupled. In my setup above, I have custom certs for my proxy, but I'd expect the HTTP client to use my default TLS system certs to communicate with ifconfig.me.
So, along with the standard ssl_context=... option, maybe we should have a proxy_ssl_context=... option that would be used for the initial CONNECT request.
Well, actually, the strategy for curl and Requests for HTTPS requests to HTTPS proxies seems to be this:
Requests: ignore the proxy altogether, just issue the HTTPS request through to the remote directly.
curl: establish the tunnel through CONNECT, but issue a normal HTTPS request afterwards.
Put simply, neither really "tunnels" HTTPS requests through the proxy — they just bypass it in practice. Or am I missing something?
Neither strategy consists in "unwrapping" the TCP/TLS connection. Also, we don't need a new proxy_ssl_context option. If the HTTPS proxy uses custom certs, users can do httpx.create_ssl_context() and .add_verify_location(...), so that the ssl_context works for both the CONNECT request as well as any other requests to regular hosts.
I'm not sure which of curl's or Requests' behavior we should adopt; basically, do we want to issue a CONNECT even though we won't be using the connection eventually? For simplicity I'd say "no, just do HTTPS directly", so, Requests' approach. But… (?) 🤷♂️
I'm realizing that urllib3 has explicit support for HTTPS + HTTPS, and it does so thanks to the stdlib's http.client implementation of HTTP Connect tunnels:
Okay so, looking at urllib3, it turns out that HTTPS proxy support there is pretty new too — and this is the PR we probably want to take inspiration from: urllib3/urllib3#1923
@elliottmoos Given how the urllib3 PR above goes into fairly low-level hoops (re-implementing httplib's HTTPSConnection to enable a "TLS-in-TLS" mode), I'd expect my snippet above to fail. It seems http.client doesn't support HTTPS proxies either. (The wording there is confusing though, it says "to connect to an HTTPS proxy…", but I think what it really means is "to make an HTTPS request through an HTTP proxy…". I've reviewed the code for http.client and with the snippet from the docs, what seems to happen is effectively what we call "tunnel w/ .start_tls()" — used for making HTTPS requests through an HTTP proxy.)
At this point I'm marking this as enhancement because it's quite clear that what we need is to add support for HTTPS proxies — that support is not there yet. :)
@florimondmanca I was just going to follow up with you on this.
It looks like the HTTPSConnection constructor doesn't have a headers kwarg available, but the set_tunnel method does (not that we'd throw the headers in there).
After reading this SO post, the ProxyManager resulted in a "connection reset" error which leads me to believe something in the auth mechanism isn't working, either on the proxy's end or in ProxyManager.
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='ifconfig.me', port=443): Max retries exceeded with url: /ip (Caused by ProxyError('Cannot connect to proxy.', ConnectionResetError(54, 'Connection reset by peer')))
I was getting similar errors with a modified version of your suggestion.
I followed the SO post's syntax exactly, fyi. What do you think?
Did you try with the latest urllib3 version? I believe HTTPS proxy support was only added in 1.26 there. To me, ConnectionResetError sounds like the proxy hard-closing the connection because of a protocol error... such as a failure to complete the TLS handshake bc the client can't do it, or something else after the CONNECT request. 🤔 So yeah, I'd check you're on 1.26, and otherwise maybe ask urllib3 folks about that?
But again though I wasn't expecting the raw http.client to work, because that doesn't support HTTPS proxies either. A ConnectionResetError makes sense there too.
@elliottmoos Hey! I'm closing this off for now, as I've logged #1434 to track HTTPS proxies support properly. We can continue discussion about your setup and use case here, but that way we have a single issue to track HTTPS proxies support (which we obviously don't actually have proper support for right now). Thanks!
Activity
florimondmanca commentedon Dec 9, 2020
@elliottmoos Hello!
First question Id have here is: does this reproduce if not setting a particular proxy mode?
The default behavior is to tunnel HTTPS requests, so given that you're requesting an HTTPS resource (ifconfig.me) I would expect the issue to remain if you just dropped the mode option and just passed:
If that's the case then we can look at the dance happening when using an HTTPS proxy URL.
Also, are you able to make requests through this HTTPS proxy using another client, such as cURL or Requests?
elliottmoos commentedon Dec 10, 2020
@florimondmanca Hi! Thanks for checking in.
I can reproduce this without the
TUNNEL_ONLYsetting, which makes sense considering the functionality you described.cURL and requests both work. Here is the output of a little test:
requests version: 2.25.0
Code:
Output:
Full Traceback
florimondmanca commentedon Dec 10, 2020
Okay, so from the traceback you should, it fails during
.start_tls(), which is the point where we wrap the TCP socket that was open by the proxy during the initialCONNECTrequest, and do the TLS handshake on it.In your case, the proxy URL is an HTTPS one, so we do the
CONNECTrequest via HTTPS, which means the TCP socket we get is already wrapped around TLS. So, it shouldn't be much of a surprise that doing a TLS handshake with the remote server on a connection that's already encrypted without the remote knowing it is bound to lead to… problems. :-) At least our.start_tls()-based approach to tunneling is obviously too naive to handle this case.Incidently, we don't actually have a proper test for + in HTTPCore (only HTTP/HTTP and HTTP/HTTPS).
We use
pproxythere, so one way to possibly reproducing this would be to setuppproxylocally with TLS certs, and then issuing a request to an HTTPS resource through that.florimondmanca commentedon Dec 10, 2020
@ElliottMoos Are you sure the Requests example gets any traffic routed to the HTTPS proxy? I tried your snippet and I can actually put anything as the
httpsproxy and the request will work, so…?As for
curl, I can confirm it works well forifconfig.me. I forced--http1.1so we're not bothered with HTTP/2 dances, and passed-vso we can see the full list of events happening:So, as we can see:
curlconnects to the proxy via TLS, doing the handshake and exchanging certs.curlissues aCONNECTrequest forexample.org:443, which succeeds.curldoes a new handshake withexample.orgover the tunnel.florimondmanca commentedon Dec 10, 2020
So, I think right now HTTPCore simply doesn't support HTTPS proxies, or wrongly so. Here's why.
Right now the
ssl_contextwe pass to the proxy transport is used for both:CONNECTrequest:https://github.com/encode/httpcore/blob/62b47316983c5309c8f7b703139d45af1cd402d8/httpcore%2F_async%2Fhttp_proxy.py#L196-L200
.start_tls():https://github.com/encode/httpcore/blob/12a25fdb311cc3f0feb2b7ca7f0afab46989fe19/httpcore%2F_async%2Fhttp11.py#L83-L88
But as
curlshows us, those two should be decoupled. In my setup above, I have custom certs for my proxy, but I'd expect the HTTP client to use my default TLS system certs to communicate withifconfig.me.So, along with the standard
ssl_context=...option, maybe we should have aproxy_ssl_context=...option that would be used for the initialCONNECTrequest.florimondmanca commentedon Dec 10, 2020
Well, actually, the strategy for curl and Requests for HTTPS requests to HTTPS proxies seems to be this:
Put simply, neither really "tunnels" HTTPS requests through the proxy — they just bypass it in practice. Or am I missing something?
Neither strategy consists in "unwrapping" the TCP/TLS connection. Also, we don't need a new
proxy_ssl_contextoption. If the HTTPS proxy uses custom certs, users can dohttpx.create_ssl_context()and.add_verify_location(...), so that thessl_contextworks for both the CONNECT request as well as any other requests to regular hosts.I'm not sure which of curl's or Requests' behavior we should adopt; basically, do we want to issue a CONNECT even though we won't be using the connection eventually? For simplicity I'd say "no, just do HTTPS directly", so, Requests' approach. But… (?) 🤷♂️
florimondmanca commentedon Dec 15, 2020
I'm realizing that
urllib3has explicit support for HTTPS + HTTPS, and it does so thanks to the stdlib'shttp.clientimplementation of HTTP Connect tunnels:https://github.com/urllib3/urllib3/blob/cad4850614d86b4db56a785bc9f984eed342f8be/src/urllib3/connectionpool.py#L910
https://docs.python.org/3/library/http.client.html#http.client.HTTPConnection.set_tunnel
@elliottmoos Curious if you could confirm that the following script works on your setup…?
Planning to take a look at what the stdlib's
.set_tunnel()implementation does.florimondmanca commentedon Dec 15, 2020
Okay so, looking at urllib3, it turns out that HTTPS proxy support there is pretty new too — and this is the PR we probably want to take inspiration from: urllib3/urllib3#1923
@elliottmoos Given how the urllib3 PR above goes into fairly low-level hoops (re-implementing httplib's
HTTPSConnectionto enable a "TLS-in-TLS" mode), I'd expect my snippet above to fail. It seemshttp.clientdoesn't support HTTPS proxies either. (The wording there is confusing though, it says "to connect to an HTTPS proxy…", but I think what it really means is "to make an HTTPS request through an HTTP proxy…". I've reviewed the code forhttp.clientand with the snippet from the docs, what seems to happen is effectively what we call "tunnel w/.start_tls()" — used for making HTTPS requests through an HTTP proxy.)florimondmanca commentedon Dec 15, 2020
At this point I'm marking this as
enhancementbecause it's quite clear that what we need is to add support for HTTPS proxies — that support is not there yet. :)elliottmoos commentedon Dec 15, 2020
@florimondmanca I was just going to follow up with you on this.
It looks like the
HTTPSConnectionconstructor doesn't have aheaderskwarg available, but theset_tunnelmethod does (not that we'd throw the headers in there).After reading this SO post, the
ProxyManagerresulted in a "connection reset" error which leads me to believe something in the auth mechanism isn't working, either on the proxy's end or inProxyManager.I was getting similar errors with a modified version of your suggestion.
I followed the SO post's syntax exactly, fyi. What do you think?
florimondmanca commentedon Dec 16, 2020
Did you try with the latest urllib3 version? I believe HTTPS proxy support was only added in 1.26 there. To me, ConnectionResetError sounds like the proxy hard-closing the connection because of a protocol error... such as a failure to complete the TLS handshake bc the client can't do it, or something else after the CONNECT request. 🤔 So yeah, I'd check you're on 1.26, and otherwise maybe ask urllib3 folks about that?
But again though I wasn't expecting the raw
http.clientto work, because that doesn't support HTTPS proxies either. AConnectionResetErrormakes sense there too.florimondmanca commentedon Dec 21, 2020
@elliottmoos Hey! I'm closing this off for now, as I've logged #1434 to track HTTPS proxies support properly. We can continue discussion about your setup and use case here, but that way we have a single issue to track HTTPS proxies support (which we obviously don't actually have proper support for right now). Thanks!