Skip to content

Commit

Permalink
Add support for per-request HTTPS traffic Proxying
Browse files Browse the repository at this point in the history
WIP on tests

linting etc.

Hacky solution to golang bug
  • Loading branch information
pspieker-stripe committed Nov 29, 2023
1 parent dbbdf2f commit def7612
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
49 changes: 39 additions & 10 deletions https.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ const (
)

var (
OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
httpsRegexp = regexp.MustCompile(`^https:\/\/`)
OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
httpsRegexp = regexp.MustCompile(`^https:\/\/`)
PerRequestHTTPSProxyHeaderKey = "X-Https-Proxy"
)

type ConnectAction struct {
Expand Down Expand Up @@ -84,8 +85,8 @@ func (proxy *ProxyHttpServer) connectDialContext(ctx *ProxyCtx, network, addr st
}

func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}

ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
hij, ok := w.(http.Hijacker)
if !ok {
panic("httpserver does not support hijacking")
Expand Down Expand Up @@ -117,8 +118,14 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
if !hasPort.MatchString(host) {
host += ":80"
}

httpsProxy, err := httpsProxyAddr(r.URL, proxy.HttpsProxyAddr)
var httpsProxyURL string
if r.Header[PerRequestHTTPSProxyHeaderKey] != nil && r.Header[PerRequestHTTPSProxyHeaderKey][0] != "" {
httpsProxyURL = r.Header[PerRequestHTTPSProxyHeaderKey][0]
} else {
httpsProxyURL = proxy.HttpsProxyAddr
}
// runtime.Breakpoint()
httpsProxy, err := httpsProxyAddr(r.URL, httpsProxyURL)
if err != nil {
ctx.Warnf("Error configuring HTTPS proxy err=%q url=%q", err, r.URL.String())
}
Expand Down Expand Up @@ -565,7 +572,7 @@ func (proxy *ProxyHttpServer) connectDialProxyWithContext(ctx *ProxyCtx, proxyHo
return c, nil
}

// httpsProxyAddr function uses the address in httpsProxy parameter.
// httpsProxyAddr function uses the address in httpsProxy parameter.
// When the httpProxyAddr parameter is empty, uses the HTTPS_PROXY, https_proxy from environment variables.
// httpsProxyAddr function allows goproxy to respect no_proxy env vars
// https://github.com/stripe/goproxy/pull/5
Expand All @@ -586,7 +593,29 @@ func httpsProxyAddr(reqURL *url.URL, httpsProxy string) (string, error) {
reqSchemeURL := reqURL
reqSchemeURL.Scheme = "https"

proxyURL, err := cfg.ProxyFunc()(reqSchemeURL)
parsedUrl, err := url.Parse(httpsProxy)
if err != nil {
return "", err
}

proxyHost, _, err := net.SplitHostPort(parsedUrl.Host)
ip := net.ParseIP(proxyHost)

var proxyURL *url.URL
// We do this because of the golang issue here:
// https://go-review.googlesource.com/c/net/+/239164?tab=comments
if parsedUrl.Host == "localhost" || (err == nil && ip != nil && ip.IsLoopback()) {
proxyURL, err = url.Parse(httpsProxy)
if err != nil {
return "", err
}
} else {
proxyURL, err = cfg.ProxyFunc()(reqSchemeURL)
if err != nil {
return "", err
}
}

if err != nil {
return "", err
}
Expand Down
48 changes: 46 additions & 2 deletions proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ func TestHttpProxyAddrsFromEnv(t *testing.T) {
os.Setenv("http_proxy", l.URL)
os.Setenv("https_proxy", l.URL)
proxy2 := goproxy.NewProxyHttpServer()

client, l2 := oneShotProxy(proxy2, t)
defer l2.Close()
if r := string(getOrFail(https.URL+"/bobo", client, t)); r != "bobo bobo" {
Expand All @@ -733,6 +733,50 @@ func TestHttpProxyAddrsFromEnv(t *testing.T) {
os.Unsetenv("https_proxy")
}

func TestOverrideHttpsProxyAddrsFromEnvWithRequest(t *testing.T) {
// The request essentially does:
// Client -> FakeStripeEgressProxy -> FakeExternalProxy -> FinalDestination
finalDestinationUrl := https.URL + "/bobo"

// TODO: figure out why this doesn't work
// We set the env vars here to ensure that our per-request config overrides these
// os.Setenv("http_proxy", "http://incorrectproxy.com")
// os.Setenv("https_proxy", "http://incorrectproxy.com")

fakeExternalProxy := goproxy.NewProxyHttpServer()
fakeExternalProxyTestStruct := httptest.NewServer(fakeExternalProxy)
fakeExternalProxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
tagExternalProxyPassthrough := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
b, err := ioutil.ReadAll(resp.Body)
panicOnErr(err, "readAll resp")
resp.Body = ioutil.NopCloser(bytes.NewBufferString(string(b) + " " + string(b)))
return resp
}
fakeExternalProxy.OnResponse().DoFunc(tagExternalProxyPassthrough)

fakeStripeEgressProxy := goproxy.NewProxyHttpServer()
fakeStripeEgressProxyTestStruct := httptest.NewServer(fakeStripeEgressProxy)

// Next, we construct the client that we'll be using to talk to our 2 proxies
egressProxyUrl, _ := url.Parse(fakeStripeEgressProxyTestStruct.URL)
tr := &http.Transport{
TLSClientConfig: acceptAllCerts,
Proxy: http.ProxyURL(egressProxyUrl),
ProxyConnectHeader: map[string][]string{
"X-Https-Proxy": {fakeExternalProxyTestStruct.URL},
},
}
client := &http.Client{Transport: tr}

r := string(getOrFail(finalDestinationUrl, client, t))
if r != "bobo bobo" {
t.Error("Expected bobo doubled twice, got", r)
}

// os.Unsetenv("http_proxy")
// os.Unsetenv("https_proxy")
}

func TestCustomHttpProxyAddrs(t *testing.T) {
proxy := goproxy.NewProxyHttpServer()
doubleString := func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
Expand All @@ -748,7 +792,7 @@ func TestCustomHttpProxyAddrs(t *testing.T) {
defer l.Close()

proxy2 := goproxy.NewProxyHttpServer(goproxy.WithHttpProxyAddr(l.URL), goproxy.WithHttpsProxyAddr(l.URL))

client, l2 := oneShotProxy(proxy2, t)
defer l2.Close()
if r := string(getOrFail(https.URL+"/bobo", client, t)); r != "bobo bobo" {
Expand Down

0 comments on commit def7612

Please sign in to comment.