Add support for HTTP_PROXY, HTTPS_PROXY, and NO_PROXY#1260
Add support for HTTP_PROXY, HTTPS_PROXY, and NO_PROXY#1260peterguy wants to merge 33 commits intopeterguy/clean-up-configfrom
Conversation
keegancsmith
left a comment
There was a problem hiding this comment.
Nice! Inline comments, but otherwise LGTM
internal/api/api.go
Outdated
| if opts.ProxyURL != nil || opts.ProxyPath != "" { | ||
| // Explicit SRC_PROXY configuration takes precedence. | ||
| transport = withProxyTransport(transport, opts.ProxyURL, opts.ProxyPath) | ||
| } else if proxyURL := envProxyURL(opts.Endpoint); proxyURL != nil && proxyURL.Scheme == "https" { |
There was a problem hiding this comment.
why does https:// not work without intervention? (as documented below for http and socks5)
There was a problem hiding this comment.
Since we're connecting to the SG API using HTTP/2, the standard transport tries to connect to the TLS-enabled proxy using HTTP/2 also, which fails if the proxy doesn't support HTTP/2. I haven't found a proxy for local testing that does support HTTP/2; I suspect that most/all proxies that are TLS-enabled don't support HTTP/2.
internal/api/proxy_test.go
Outdated
| ln, err := net.Listen("tcp", "127.0.0.1:0") | ||
| if err != nil { | ||
| t.Fatalf("proxy listen: %v", err) | ||
| } | ||
|
|
||
| srv := &http.Server{Handler: handler} | ||
|
|
||
| if useTLS { | ||
| cert := generateTestCert(t, "127.0.0.1") | ||
| srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} | ||
| go srv.ServeTLS(ln, "", "") | ||
| } else { | ||
| go srv.Serve(ln) | ||
| } | ||
| t.Cleanup(func() { srv.Close() }) | ||
|
|
||
| // Wait for the server to be ready | ||
| waitForServerReady(t, ln.Addr().String(), useTLS, 5*time.Second) | ||
|
|
||
| scheme := "http" | ||
| if useTLS { | ||
| scheme = "https" | ||
| } | ||
| pURL, _ := url.Parse(fmt.Sprintf("%s://%s", scheme, ln.Addr().String())) | ||
| return pURL, ch |
There was a problem hiding this comment.
I think you can greatly simplify code here (and remove helpers like waitForServerReady and generateTestCert) by using the httptest package. It already provides functions to listen on TLS/etc and does things like wait for the server to be listening.
| {"https with port", "https://proxy.example.com:8443", "proxy.example.com:8443"}, | ||
| {"https without port", "https://proxy.example.com", "proxy.example.com:443"}, | ||
| {"http with port", "http://proxy.example.com:8080", "proxy.example.com:8080"}, | ||
| {"http without port", "http://proxy.example.com", "proxy.example.com:80"}, |
There was a problem hiding this comment.
get our favourite AI's to generate some example with ipv4, ipv6 and localhost.
|
You might wanna rebase, I think william just did some CI updates which include things like go-lint/etc. |
|
Thanks for all of your commentary; I forgot to open this PR in draft mode! Been trying to address what looks like flakey tests in the Ubuntu CI runner; I'll go through your very helpful comments soon. |
|
This change is part of the following stack: Change managed by git-spice. |
b1d6176 to
05f0fbb
Compare
|
@peterguy alright just re-request review when ready. |
622bd60 to
3536377
Compare
keegancsmith
left a comment
There was a problem hiding this comment.
Requesting changes since I just wanna re-review once you have addressed the comments. FYI I pushed some commits to resolve the CI issues.
| } | ||
| if !isValidUDS { | ||
| return nil, errors.Newf("invalid proxy socket: %s", path) | ||
| return nil, errors.Newf("Invalid proxy socket: %s", path) |
There was a problem hiding this comment.
you changed this is some other places as well. go convention is lowercase on error messages since they get combined. eg My outer error: My inner error doesn't read as nicely as my outer error: my inner error
| // For http:// and socks5:// proxies, the cloned | ||
| // transport's default Proxy handles them correctly without intervention. |
There was a problem hiding this comment.
if I set SRC_PROXY to a http:// or socks5:// though, won't the default transport ignore it?
There was a problem hiding this comment.
I re-worked this section
internal/api/proxy_test.go
Outdated
| ln, err := net.Listen("tcp", "127.0.0.1:0") | ||
| if err != nil { | ||
| t.Fatalf("proxy listen: %v", err) | ||
| } | ||
|
|
||
| srv := &http.Server{Handler: handler} | ||
|
|
||
| if useTLS { | ||
| cert := generateTestCert(t, "127.0.0.1") | ||
| srv.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cert}} | ||
| go srv.ServeTLS(ln, "", "") | ||
| } else { | ||
| go srv.Serve(ln) | ||
| } | ||
| t.Cleanup(func() { srv.Close() }) | ||
|
|
||
| // Wait for the server to be ready | ||
| waitForServerReady(t, ln.Addr().String(), useTLS, 5*time.Second) | ||
|
|
||
| scheme := "http" | ||
| if useTLS { | ||
| scheme = "https" | ||
| } | ||
| pURL, _ := url.Parse(fmt.Sprintf("%s://%s", scheme, ln.Addr().String())) | ||
| return pURL, ch |
internal/api/api.go
Outdated
| } | ||
| s += fmt.Sprintf(" %s \\\n", shellquote.Join("-d", string(data))) | ||
| s += fmt.Sprintf(" %s", shellquote.Join(r.client.opts.Endpoint+"/.api/graphql")) | ||
| s += fmt.Sprintf(" %s", shellquote.Join(r.client.opts.EndpointURL.String()+"/.api/graphql")) |
There was a problem hiding this comment.
given you are doing TrimRight before you should probably do it here as well.
| func (c *connWithBufferedReader) Read(p []byte) (int, error) { | ||
| return c.r.Read(p) | ||
| } |
There was a problem hiding this comment.
net.Conn documents all methods to be concurrency safe. However, bufio.Reader is not. You need to add in a mutex here.
> Multiple goroutines may invoke methods on a Conn simultaneously.
|
|
||
| // Read and check the response from the proxy | ||
| resp, err := http.ReadResponse(bufio.NewReader(conn), nil) | ||
| br := bufio.NewReader(conn) |
There was a problem hiding this comment.
It isn't clear to me why you introduced the buffered reader?
There was a problem hiding this comment.
Because I didn't want to lose any bytes after the headers that the reader may have buffered, which bytes could be the start of the tunneled connection data. Granted, it's probably not really a problem because the SG endpoint will in nearly every scenario also be TLS, and "TLS server will not speak until spoken to.", so there will not be any bytes beyond the header. But if the SG endpoint is plain http, we could lose bytes if we discard the buffered reader.
cmd/src/main_test.go
Outdated
| EndpointURL: &url.URL{ | ||
| Scheme: "https", | ||
| Host: "override.com", | ||
| }, |
There was a problem hiding this comment.
is it worth updating all these test cases for this? I'd consider just not comparing this field in the comparsion in the test.
|
I would wait for an update for Keegan's requested changes before doing my review pass if that's okay. |
6ab67e5 to
78eadd2
Compare
78eadd2 to
54aada1
Compare
cab95e8 to
97c1b0a
Compare
69221c1 to
791a15c
Compare
…SRC_PROXY if present
…quest.Write to ensure correctness
…y edge cases of silently dropped/ignored pieces of the response from the proxy
…e sure it'll try HTTP/2 by setting NextProtos
… env up front so that typos and malformed urls will surface immediately
…e proxy in the config is now the one gathered from either SRC_PROXY or the standard env variables
… that proxies are used
…tlsConn.Close()`) is necessary
…ng in the custom dialer
5b46ace to
dff0820
Compare
Adds support for the standard environment variables
HTTP_PROXY,HTTPS_PROXY, andNO_PROXY.SRC_PROXYstill takes precedence, including overNO_PROXY.Also added fixes for connecting to TLS-enabled proxies, where we want to connect to the SG API using HTTP/2, but many proxies support only HTTP/1.1, so we need to connect to the proxy via HTTP/1.1, then connect through the proxy to the SG API using HTTP/2.
Moved endpoint and proxy parsing to the config building so that typos and malformed urls are caught early and errors returned immediately.
Test plan
Added CI tests validating proxy behavior.
Manual testing
Install
mitmproxyandsquid.Create an SSL cert for
squid:Create a config for
squid:Run mitmproxy -
mitmproxy -v- in one terminal.Run squid -
squid -f ./squid.conf -N -d 1- in another terminal.In a third terminal, in the src-cli repo, build the binary:
go build -o ./src-cli ./cmd/srcEnsure you have
SRC_ENDPOINTandSRC_ACCESS_TOKENset in your environment, and unsetSRC_PROXY.Try connecting with each proxy (
mitmproxyis 8080,squidis 8445):You should get "Authenticated as [USER] on [ENDPOINT]" messages in your terminal, and in the
mitmproxyandsquidterminals, you should see activity.This tests connecting through a proxy that connects with http (
mitmproxy), and one that is TLS-enabled (squid).Ensure that
NO_PROXYis ignored when usingSRC_PROXYand honored when usingHTTPS_PROXY:You should get a successful login, but no entry in the
mitmproxylog.You should get a successful login and also see an entry in the
mitmproxylog.Closes https://linear.app/sourcegraph/issue/CPL-236/src-cli-support-standard-http-proxy