aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/transport/http
diff options
context:
space:
mode:
authorSanskar Jaiswal <jaiswalsanskar078@gmail.com>2023-04-28 17:17:00 +0530
committerSanskar Jaiswal <jaiswalsanskar078@gmail.com>2023-05-04 11:53:09 +0530
commita830187d90a6bc36f9466c075ed49076f591efa9 (patch)
tree52c3450287073b3d8f65b2c001f9c7150cc66b9d /plumbing/transport/http
parent399b1ec2d598b7950816727b8d92e8580553372c (diff)
downloadgo-git-a830187d90a6bc36f9466c075ed49076f591efa9.tar.gz
plumbing: transport/http, add support for custom proxy URLs
Add support for custom HTTP and HTTPS proxies for each session. The tests require server certificate and a matching private key to be able to run a TLS server and test HTTPS proxy functionality. The cert and the key are stored in `plumbing/transport/http/testdata/certs` and were generated using the following command: `openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt` Note: All details were left empty, except for the FQDN for which example.com was used. Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
Diffstat (limited to 'plumbing/transport/http')
-rw-r--r--plumbing/transport/http/common.go22
-rw-r--r--plumbing/transport/http/internal/test/proxy_test.go72
-rw-r--r--plumbing/transport/http/internal/test/test_utils.go43
-rw-r--r--plumbing/transport/http/proxy_test.go119
-rw-r--r--plumbing/transport/http/testdata/certs/server.crt22
-rw-r--r--plumbing/transport/http/testdata/certs/server.key28
-rw-r--r--plumbing/transport/http/transport.go2
7 files changed, 307 insertions, 1 deletions
diff --git a/plumbing/transport/http/common.go b/plumbing/transport/http/common.go
index 5300341..f9b7a0e 100644
--- a/plumbing/transport/http/common.go
+++ b/plumbing/transport/http/common.go
@@ -9,6 +9,7 @@ import (
"fmt"
"net"
"net/http"
+ "net/url"
"reflect"
"strconv"
"strings"
@@ -188,6 +189,10 @@ func transportWithCABundle(transport *http.Transport, caBundle []byte) error {
return nil
}
+func transportWithProxy(transport *http.Transport, proxyURL *url.URL) {
+ transport.Proxy = http.ProxyURL(proxyURL)
+}
+
func configureTransport(transport *http.Transport, ep *transport.Endpoint) error {
if len(ep.CaBundle) > 0 {
if err := transportWithCABundle(transport, ep.CaBundle); err != nil {
@@ -197,6 +202,14 @@ func configureTransport(transport *http.Transport, ep *transport.Endpoint) error
if ep.InsecureSkipTLS {
transportWithInsecureTLS(transport)
}
+
+ if ep.Proxy.URL != "" {
+ proxyURL, err := ep.Proxy.FullURL()
+ if err != nil {
+ return err
+ }
+ transportWithProxy(transport, proxyURL)
+ }
return nil
}
@@ -205,7 +218,7 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*
// We need to configure the http transport if there are transport specific
// options present in the endpoint.
- if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS {
+ if len(ep.CaBundle) > 0 || ep.InsecureSkipTLS || ep.Proxy.URL != "" {
var transport *http.Transport
// if the client wasn't configured to have a cache for transports then just configure
// the transport and use it directly, otherwise try to use the cache.
@@ -223,6 +236,13 @@ func newSession(c *client, ep *transport.Endpoint, auth transport.AuthMethod) (*
caBundle: string(ep.CaBundle),
insecureSkipTLS: ep.InsecureSkipTLS,
}
+ if ep.Proxy.URL != "" {
+ proxyURL, err := ep.Proxy.FullURL()
+ if err != nil {
+ return nil, err
+ }
+ transportOpts.proxyURL = *proxyURL
+ }
var found bool
transport, found = c.fetchTransport(transportOpts)
diff --git a/plumbing/transport/http/internal/test/proxy_test.go b/plumbing/transport/http/internal/test/proxy_test.go
new file mode 100644
index 0000000..6ae2943
--- /dev/null
+++ b/plumbing/transport/http/internal/test/proxy_test.go
@@ -0,0 +1,72 @@
+package test
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ nethttp "net/http"
+ "os"
+ "sync/atomic"
+ "testing"
+
+ "github.com/elazarl/goproxy"
+
+ "github.com/go-git/go-git/v5/plumbing/transport"
+ "github.com/go-git/go-git/v5/plumbing/transport/http"
+
+ . "gopkg.in/check.v1"
+)
+
+// Hook up gocheck into the "go test" runner.
+func Test(t *testing.T) { TestingT(t) }
+
+type ProxySuite struct{}
+
+var _ = Suite(&ProxySuite{})
+
+var proxiedRequests int32
+
+// This test tests proxy support via an env var, i.e. `HTTPS_PROXY`.
+// Its located in a separate package because golang caches the value
+// of proxy env vars leading to misleading/unexpected test results.
+func (s *ProxySuite) TestAdvertisedReferences(c *C) {
+ proxy := goproxy.NewProxyHttpServer()
+ proxy.Verbose = true
+ SetupHTTPSProxy(proxy, &proxiedRequests)
+ httpsListener, err := net.Listen("tcp", ":0")
+ c.Assert(err, IsNil)
+ defer httpsListener.Close()
+ httpProxyAddr := fmt.Sprintf("localhost:%d", httpsListener.Addr().(*net.TCPAddr).Port)
+
+ proxyServer := nethttp.Server{
+ Addr: httpProxyAddr,
+ Handler: proxy,
+ // Due to how golang manages http/2 when provided with custom TLS config,
+ // servers and clients running in the same process leads to issues.
+ // Ref: https://github.com/golang/go/issues/21336
+ TLSConfig: &tls.Config{
+ NextProtos: []string{"http/1.1"},
+ },
+ }
+ go proxyServer.ServeTLS(httpsListener, "../../testdata/certs/server.crt", "../../testdata/certs/server.key")
+ defer proxyServer.Close()
+ os.Setenv("HTTPS_PROXY", fmt.Sprintf("https://user:pass@%s", httpProxyAddr))
+ defer os.Unsetenv("HTTPS_PROXY")
+
+ endpoint, err := transport.NewEndpoint("https://github.com/git-fixtures/basic.git")
+ c.Assert(err, IsNil)
+ endpoint.InsecureSkipTLS = true
+
+ client := http.DefaultClient
+ session, err := client.NewUploadPackSession(endpoint, nil)
+ c.Assert(err, IsNil)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ info, err := session.AdvertisedReferencesContext(ctx)
+ c.Assert(err, IsNil)
+ c.Assert(info, NotNil)
+ proxyUsed := atomic.LoadInt32(&proxiedRequests) > 0
+ c.Assert(proxyUsed, Equals, true)
+}
diff --git a/plumbing/transport/http/internal/test/test_utils.go b/plumbing/transport/http/internal/test/test_utils.go
new file mode 100644
index 0000000..6665fb3
--- /dev/null
+++ b/plumbing/transport/http/internal/test/test_utils.go
@@ -0,0 +1,43 @@
+package test
+
+import (
+ "encoding/base64"
+ "strings"
+ "sync/atomic"
+
+ "github.com/elazarl/goproxy"
+)
+
+func SetupHTTPSProxy(proxy *goproxy.ProxyHttpServer, proxiedRequests *int32) {
+ var proxyHandler goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
+ if strings.Contains(host, "github.com") {
+ user, pass, _ := ParseBasicAuth(ctx.Req.Header.Get("Proxy-Authorization"))
+ if user != "user" || pass != "pass" {
+ return goproxy.RejectConnect, host
+ }
+ atomic.AddInt32(proxiedRequests, 1)
+ return goproxy.OkConnect, host
+ }
+ // Reject if it isn't our request.
+ return goproxy.RejectConnect, host
+ }
+ proxy.OnRequest().HandleConnect(proxyHandler)
+}
+
+// adapted from https://github.com/golang/go/blob/2ef70d9d0f98832c8103a7968b195e560a8bb262/src/net/http/request.go#L959
+func ParseBasicAuth(auth string) (username, password string, ok bool) {
+ const prefix = "Basic "
+ if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
+ return "", "", false
+ }
+ c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
+ if err != nil {
+ return "", "", false
+ }
+ cs := string(c)
+ username, password, ok = strings.Cut(cs, ":")
+ if !ok {
+ return "", "", false
+ }
+ return username, password, true
+}
diff --git a/plumbing/transport/http/proxy_test.go b/plumbing/transport/http/proxy_test.go
new file mode 100644
index 0000000..f3024da
--- /dev/null
+++ b/plumbing/transport/http/proxy_test.go
@@ -0,0 +1,119 @@
+package http
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+ "sync/atomic"
+
+ "github.com/elazarl/goproxy"
+ fixtures "github.com/go-git/go-git-fixtures/v4"
+ "github.com/go-git/go-git/v5/plumbing/transport"
+ "github.com/go-git/go-git/v5/plumbing/transport/http/internal/test"
+
+ . "gopkg.in/check.v1"
+)
+
+type ProxySuite struct {
+ u UploadPackSuite
+ fixtures.Suite
+}
+
+var _ = Suite(&ProxySuite{})
+
+var proxiedRequests int32
+
+func (s *ProxySuite) TestAdvertisedReferences(c *C) {
+ s.u.SetUpTest(c)
+ proxy := goproxy.NewProxyHttpServer()
+ proxy.Verbose = true
+ setupHTTPProxy(proxy, &proxiedRequests)
+ httpListener, err := net.Listen("tcp", ":0")
+ c.Assert(err, IsNil)
+ defer httpListener.Close()
+
+ httpProxyAddr := fmt.Sprintf("http://localhost:%d", httpListener.Addr().(*net.TCPAddr).Port)
+ proxyServer := http.Server{
+ Addr: httpProxyAddr,
+ Handler: proxy,
+ }
+ go proxyServer.Serve(httpListener)
+ defer proxyServer.Close()
+
+ endpoint := s.u.prepareRepository(c, fixtures.Basic().One(), "basic.git")
+ endpoint.Proxy = transport.ProxyOptions{
+ URL: httpProxyAddr,
+ Username: "user",
+ Password: "pass",
+ }
+
+ s.u.Client = NewClient(nil)
+ session, err := s.u.Client.NewUploadPackSession(endpoint, nil)
+ c.Assert(err, IsNil)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ info, err := session.AdvertisedReferencesContext(ctx)
+ c.Assert(err, IsNil)
+ c.Assert(info, NotNil)
+ proxyUsed := atomic.LoadInt32(&proxiedRequests) > 0
+ c.Assert(proxyUsed, Equals, true)
+
+ atomic.StoreInt32(&proxiedRequests, 0)
+ test.SetupHTTPSProxy(proxy, &proxiedRequests)
+ httpsListener, err := net.Listen("tcp", ":0")
+ c.Assert(err, IsNil)
+ defer httpsListener.Close()
+ httpsProxyAddr := fmt.Sprintf("https://localhost:%d", httpsListener.Addr().(*net.TCPAddr).Port)
+
+ tlsProxyServer := http.Server{
+ Addr: httpsProxyAddr,
+ Handler: proxy,
+ // Due to how golang manages http/2 when provided with custom TLS config,
+ // servers and clients running in the same process leads to issues.
+ // Ref: https://github.com/golang/go/issues/21336
+ TLSConfig: &tls.Config{
+ NextProtos: []string{"http/1.1"},
+ },
+ }
+ go tlsProxyServer.ServeTLS(httpsListener, "testdata/certs/server.crt", "testdata/certs/server.key")
+ defer tlsProxyServer.Close()
+
+ endpoint, err = transport.NewEndpoint("https://github.com/git-fixtures/basic.git")
+ c.Assert(err, IsNil)
+ endpoint.Proxy = transport.ProxyOptions{
+ URL: httpsProxyAddr,
+ Username: "user",
+ Password: "pass",
+ }
+ endpoint.InsecureSkipTLS = true
+
+ session, err = s.u.Client.NewUploadPackSession(endpoint, nil)
+ c.Assert(err, IsNil)
+
+ info, err = session.AdvertisedReferencesContext(ctx)
+ c.Assert(err, IsNil)
+ c.Assert(info, NotNil)
+ proxyUsed = atomic.LoadInt32(&proxiedRequests) > 0
+ c.Assert(proxyUsed, Equals, true)
+}
+
+func setupHTTPProxy(proxy *goproxy.ProxyHttpServer, proxiedRequests *int32) {
+ // The request is being forwarded to the local test git server in this handler.
+ var proxyHandler goproxy.FuncReqHandler = func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
+ if strings.Contains(req.Host, "localhost") {
+ user, pass, _ := test.ParseBasicAuth(req.Header.Get("Proxy-Authorization"))
+ if user != "user" || pass != "pass" {
+ return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusUnauthorized, "")
+ }
+ atomic.AddInt32(proxiedRequests, 1)
+ return req, nil
+ }
+ // Reject if it isn't our request.
+ return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "")
+ }
+ proxy.OnRequest().Do(proxyHandler)
+}
diff --git a/plumbing/transport/http/testdata/certs/server.crt b/plumbing/transport/http/testdata/certs/server.crt
new file mode 100644
index 0000000..9bdec2c
--- /dev/null
+++ b/plumbing/transport/http/testdata/certs/server.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIUWcuzUyG3EfGsXVUH0BAmnCJyNHswDQYJKoZIhvcNAQEL
+BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
+DTIzMDMwNzA3MTgwNloXDTI0MDMwNjA3MTgwNlowWTELMAkGA1UEBhMCQVUxEzAR
+BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
+IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAvyKX6vJXt1u+WBfBNJByFDAb7msdsk6SiPFlX5uyilaWmlRxvLo1
+GZMjjuQbs4wU6BAoZcgiELnsC9GSyxgrhk7NIW3ud/QD7s8ZxETxFLb0ur6tJj7/
+ETEcU/AKSl1FpeJbGHqGipYp5+0GU0zPDxRYqC2N3+fcGZPQbBwxb1f+MrBjWutb
+3eNYTLdPH3W7RUqbunC1KZRJ8XOcU5XZ4qEaMkZYdz1QItxwPnpPuSZs53ga3TDF
+zclpQcT8OH2JNwSI6bwlwFJ0Es06manw7XHmgd8anhix9FdsQYaTOW4kqh1iKQ/P
+jPG50bdTUEqlOsaa+9av/qf+90npzt3xqQIDAQABo1MwUTAdBgNVHQ4EFgQUqTqb
+q+jiJVgwftQS+YLcQWnvTuAwHwYDVR0jBBgwFoAUqTqbq+jiJVgwftQS+YLcQWnv
+TuAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVUaFSikxyCy1
+4P/ZZgeuR7vEJ5vWBxKPw/jFNZUFWy2Ag32w1BhrDwoYoc1Awg76QF2TqBQAhFNm
+ek9aE+L83P/R2UhE9+LHnzwdMXt9HYOI1grONk2z3lMI1y4FCJBxHfGyC/XMoNgZ
+qP7UdLgLGTIMN3O1Fww416Hn8BHzxN4o5ZEHJZ6QPMuN8OLk9oVu3yQIq/QcmSDH
+GT2RiwT5IJWMUKK1UrV+y3/T9FwW2qqu+LX+coxjk7HgDWy3y66V9ahLBt8kONcr
+qK0zutoQ5WPSmvnD2Nr0LVLGXEd7hbQNO7bgjO2YOBtnagUQJt72i/OmvZv8Mfnp
+Bu6Qgl5hDw==
+-----END CERTIFICATE-----
diff --git a/plumbing/transport/http/testdata/certs/server.key b/plumbing/transport/http/testdata/certs/server.key
new file mode 100644
index 0000000..9a0cd8f
--- /dev/null
+++ b/plumbing/transport/http/testdata/certs/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/Ipfq8le3W75Y
+F8E0kHIUMBvuax2yTpKI8WVfm7KKVpaaVHG8ujUZkyOO5BuzjBToEChlyCIQuewL
+0ZLLGCuGTs0hbe539APuzxnERPEUtvS6vq0mPv8RMRxT8ApKXUWl4lsYeoaKlinn
+7QZTTM8PFFioLY3f59wZk9BsHDFvV/4ysGNa61vd41hMt08fdbtFSpu6cLUplEnx
+c5xTldnioRoyRlh3PVAi3HA+ek+5JmzneBrdMMXNyWlBxPw4fYk3BIjpvCXAUnQS
+zTqZqfDtceaB3xqeGLH0V2xBhpM5biSqHWIpD8+M8bnRt1NQSqU6xpr71q/+p/73
+SenO3fGpAgMBAAECggEAQUjenQhzv5Rdmpdajcq8vHqGP9Rki0/dK1tQpex3elsD
+C+nGA5GSq46feaIeeCBjz7QdKE7Im+/1WUAXJLm3vCNUW5PB/UTixwIEKg7mTY4E
+X3jbiZHA661boKv/x9C+BmAff2fyZonN/ILwQymcG+l2MtOEfzMh8baUXSjwFbhg
+B08u4iXjee0y9I0CGMYWfasHLOIuhACCFKtqnvdQp8B82g8eSPhme5IjfPP8KZVr
+00n6z8m00HVk6/yYJ8pVZ82j3T+wH6IqvlvaC320sbto8YXV6i8GWHaJumzU4/9s
+IRm4459E+NmNcLNY/TCu89zsfrgNirN+qFfvJIOTxQKBgQDtME8s4UP0MhGuJ2lD
+1HD64fAxMC6Xp/QSzY91Yn79UNssUUV+IwjuUnLIz3U8DBs/QETLm7CkNtI7h5m5
+dBdeBBzCRGxhe8WqRfvceu4s0zr08ZkDaKLjFsBSnBsXZhKAAuRqBjnGAoAiKgVa
+WpEAug00ThhQjipSY9tO9NSBawKBgQDOSz+8m2HJFktEdSctKIB9DesqlAg7YCyy
+dHzywP0/r7wEvsCN7xPgCT5g8JBkRaFvLLKgw7gMKAUx8V2iwizEoDCAs/pbTWji
+uZwPC8lWtbkpBMQIaP4Wap+GyFQJKv1/qZduwpkwkj+ok+m3WwIW55VFGyLn3XGG
+VcLZm83aOwKBgQDXXI/nXjqHVZb8HEjWD+Ttx4yB/Q+xIAzbrc3edap8c5guKzUA
+DOulCTOz5bq65PsweTh970V6NVS6PKt12lUFRpKeSeZmtS2LJ7RCQ1RTWxAjK+MV
+V0LfEt9ZouhuXH3bwcSICFMY2VhirOjjW2xhzo0Cuw4UxqDi4kxU6rSxNQKBgQCI
+sn5KmV/jot0/QK40E0mJFEcHkM4foiwcGGqPZWiq4eUh89CefJTb+OQX0nCrsSQ3
+ChRXyTlU/NPsczcL2cVWiZt6PUihZZsh2cJaigHhbkuCrcDEneX4rrCE3IwrAwy1
+oohRAawG7nI2X8UYFbs9uDlGcKPhpvBKBtw13DM87wKBgE8fOiFoTph//6piU7dV
+pN33UfhPcAFwsIzxAH6Ljo6BYx2hfPRCxI2g0wchk6ydbDecLgMwVgugdJZ6+tRf
+P+YV3wEwPcWOvWby3+EmJh0cXUTl6ZMA+eR4pvCi6kf2xJf9dRmEeNNhOuzn9Y0J
+cT9yhBFG4iejKP0iTwET1JKY
+-----END PRIVATE KEY-----
diff --git a/plumbing/transport/http/transport.go b/plumbing/transport/http/transport.go
index cd6787a..052f3c8 100644
--- a/plumbing/transport/http/transport.go
+++ b/plumbing/transport/http/transport.go
@@ -2,6 +2,7 @@ package http
import (
"net/http"
+ "net/url"
)
// transportOptions contains transport specific configuration.
@@ -9,6 +10,7 @@ type transportOptions struct {
insecureSkipTLS bool
// []byte is not comparable.
caBundle string
+ proxyURL url.URL
}
func (c *client) addTransport(opts transportOptions, transport *http.Transport) {