diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index e0169f700..605d65943 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -149,6 +149,7 @@ func NewDirect(opts Options) (*Direct, error) { dialer := netns.NewDialer() tr := http.DefaultTransport.(*http.Transport).Clone() tr.Proxy = tshttpproxy.ProxyFromEnvironment + tshttpproxy.SetTransportGetProxyConnectHeader(tr) tr.DialContext = dialer.DialContext tr.ForceAttemptHTTP2 = true tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig) diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index d902a4074..4230a3852 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -589,7 +589,15 @@ func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, pr }() target := net.JoinHostPort(n.HostName, "443") - if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", target, pu.Hostname()); err != nil { + + var authHeader string + if v, err := tshttpproxy.GetAuthHeader(pu); err != nil { + c.logf("derphttp: error getting proxy auth header for %v: %v", proxyURL, err) + } else if v != "" { + authHeader = fmt.Sprintf("Authorization: %s\r\n", v) + } + + if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n%s\r\n", target, pu.Hostname(), authHeader); err != nil { if ctx.Err() != nil { return nil, ctx.Err() } diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index eca6262c7..82269b199 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -433,6 +433,7 @@ func newLogtailTransport(host string) *http.Transport { tr := http.DefaultTransport.(*http.Transport).Clone() tr.Proxy = tshttpproxy.ProxyFromEnvironment + tshttpproxy.SetTransportGetProxyConnectHeader(tr) // We do our own zstd compression on uploads, and responses never contain any payload, // so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there diff --git a/net/tshttpproxy/tshttpproxy.go b/net/tshttpproxy/tshttpproxy.go index b14c4386d..03d604568 100644 --- a/net/tshttpproxy/tshttpproxy.go +++ b/net/tshttpproxy/tshttpproxy.go @@ -41,3 +41,16 @@ func GetAuthHeader(u *url.URL) (string, error) { } return "", nil } + +var condSetTransportGetProxyConnectHeader func(*http.Transport) + +// SetTarnsportGetProxyConnectHeader sets the provided Transport's +// GetProxyConnectHeader field, if the current build of Go supports +// it. +// +// See https://github.com/golang/go/issues/41048. +func SetTransportGetProxyConnectHeader(tr *http.Transport) { + if f := condSetTransportGetProxyConnectHeader; f != nil { + f(tr) + } +} diff --git a/net/tshttpproxy/tshttpproxy_future.go b/net/tshttpproxy/tshttpproxy_future.go new file mode 100644 index 000000000..c50d4e5d9 --- /dev/null +++ b/net/tshttpproxy/tshttpproxy_future.go @@ -0,0 +1,37 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build tailscale_go + +// We want to use https://github.com/golang/go/issues/41048 but it's only in the +// Tailscale Go tree for now. Hence the build tag above. + +package tshttpproxy + +import ( + "context" + "log" + "net/http" + "net/url" + "os" +) + +func init() { + condSetTransportGetProxyConnectHeader = func(tr *http.Transport) { + tr.GetProxyConnectHeader = func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) { + v, err := GetAuthHeader(proxyURL) + if err != nil { + log.Printf("failed to get proxy Auth header for %v; ignoring: %v", proxyURL, err) + return nil, nil + } + if fake := os.Getenv("TS_DEBUG_FAKE_PROXY_AUTH"); fake != "" { + v = fake + } + if v == "" { + return nil, nil + } + return http.Header{"Authorization": []string{v}}, nil + } + } +}