aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plumbing/transport/ssh/common.go30
-rw-r--r--plumbing/transport/ssh/internal/test/proxy_test.go113
-rw-r--r--plumbing/transport/ssh/internal/test/test_utils.go83
-rw-r--r--plumbing/transport/ssh/proxy_test.go72
-rw-r--r--plumbing/transport/ssh/upload_pack_test.go3
5 files changed, 287 insertions, 14 deletions
diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go
index e06958a..6617d9b 100644
--- a/plumbing/transport/ssh/common.go
+++ b/plumbing/transport/ssh/common.go
@@ -4,6 +4,7 @@ package ssh
import (
"context"
"fmt"
+ "net"
"reflect"
"strconv"
"strings"
@@ -139,7 +140,7 @@ func (c *command) connect() error {
overrideConfig(c.config, config)
- c.client, err = dial("tcp", hostWithPort, config)
+ c.client, err = dial("tcp", hostWithPort, c.endpoint.Proxy, config)
if err != nil {
return err
}
@@ -154,7 +155,7 @@ func (c *command) connect() error {
return nil
}
-func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
+func dial(network, addr string, proxyOpts transport.ProxyOptions, config *ssh.ClientConfig) (*ssh.Client, error) {
var (
ctx = context.Background()
cancel context.CancelFunc
@@ -166,10 +167,33 @@ func dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
}
defer cancel()
- conn, err := proxy.Dial(ctx, network, addr)
+ var conn net.Conn
+ var err error
+
+ if proxyOpts.URL != "" {
+ proxyUrl, err := proxyOpts.FullURL()
+ if err != nil {
+ return nil, err
+ }
+ dialer, err := proxy.FromURL(proxyUrl, proxy.Direct)
+ if err != nil {
+ return nil, err
+ }
+
+ // Try to use a ContextDialer, but fall back to a Dialer if that goes south.
+ ctxDialer, ok := dialer.(proxy.ContextDialer)
+ if !ok {
+ return nil, fmt.Errorf("expected ssh proxy dialer to be of type %s; got %s",
+ reflect.TypeOf(ctxDialer), reflect.TypeOf(dialer))
+ }
+ conn, err = ctxDialer.DialContext(ctx, "tcp", addr)
+ } else {
+ conn, err = proxy.Dial(ctx, network, addr)
+ }
if err != nil {
return nil, err
}
+
c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
if err != nil {
return nil, err
diff --git a/plumbing/transport/ssh/internal/test/proxy_test.go b/plumbing/transport/ssh/internal/test/proxy_test.go
new file mode 100644
index 0000000..8baac2b
--- /dev/null
+++ b/plumbing/transport/ssh/internal/test/proxy_test.go
@@ -0,0 +1,113 @@
+package test
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net"
+ "os"
+ "path/filepath"
+ "sync/atomic"
+ "testing"
+
+ "github.com/armon/go-socks5"
+ "github.com/gliderlabs/ssh"
+ "github.com/go-git/go-git/v5/plumbing/transport"
+ ggssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
+
+ fixtures "github.com/go-git/go-git-fixtures/v4"
+ stdssh "golang.org/x/crypto/ssh"
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type ProxyEnvSuite struct {
+ fixtures.Suite
+ port int
+ base string
+}
+
+var _ = Suite(&ProxyEnvSuite{})
+
+var socksProxiedRequests int32
+
+// This test tests proxy support via an env var, i.e. `ALL_PROXY`.
+// Its located in a separate package because golang caches the value
+// of proxy env vars leading to misleading/unexpected test results.
+func (s *ProxyEnvSuite) TestCommand(c *C) {
+ socksListener, err := net.Listen("tcp", "localhost:0")
+ c.Assert(err, IsNil)
+
+ socksServer, err := socks5.New(&socks5.Config{
+ Rules: TestProxyRule{},
+ })
+ c.Assert(err, IsNil)
+ go func() {
+ socksServer.Serve(socksListener)
+ }()
+ socksProxyAddr := fmt.Sprintf("socks5://localhost:%d", socksListener.Addr().(*net.TCPAddr).Port)
+ os.Setenv("ALL_PROXY", socksProxyAddr)
+ defer os.Unsetenv("ALL_PROXY")
+
+ sshListener, err := net.Listen("tcp", "localhost:0")
+ c.Assert(err, IsNil)
+ sshServer := &ssh.Server{Handler: HandlerSSH}
+ go func() {
+ log.Fatal(sshServer.Serve(sshListener))
+ }()
+
+ s.port = sshListener.Addr().(*net.TCPAddr).Port
+ s.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.port))
+ c.Assert(err, IsNil)
+
+ ggssh.DefaultAuthBuilder = func(user string) (ggssh.AuthMethod, error) {
+ return &ggssh.Password{User: user}, nil
+ }
+
+ ep := s.prepareRepository(c, fixtures.Basic().One(), "basic.git")
+ c.Assert(err, IsNil)
+
+ client := ggssh.NewClient(&stdssh.ClientConfig{
+ HostKeyCallback: stdssh.InsecureIgnoreHostKey(),
+ })
+ r, err := client.NewUploadPackSession(ep, nil)
+ c.Assert(err, IsNil)
+ defer func() { c.Assert(r.Close(), IsNil) }()
+
+ info, err := r.AdvertisedReferences()
+ c.Assert(err, IsNil)
+ c.Assert(info, NotNil)
+ proxyUsed := atomic.LoadInt32(&socksProxiedRequests) > 0
+ c.Assert(proxyUsed, Equals, true)
+}
+
+func (s *ProxyEnvSuite) prepareRepository(c *C, f *fixtures.Fixture, name string) *transport.Endpoint {
+ fs := f.DotGit()
+
+ err := fixtures.EnsureIsBare(fs)
+ c.Assert(err, IsNil)
+
+ path := filepath.Join(s.base, name)
+ err = os.Rename(fs.Root(), path)
+ c.Assert(err, IsNil)
+
+ return s.newEndpoint(c, name)
+}
+
+func (s *ProxyEnvSuite) newEndpoint(c *C, name string) *transport.Endpoint {
+ ep, err := transport.NewEndpoint(fmt.Sprintf(
+ "ssh://git@localhost:%d/%s/%s", s.port, filepath.ToSlash(s.base), name,
+ ))
+
+ c.Assert(err, IsNil)
+ return ep
+}
+
+type TestProxyRule struct{}
+
+func (dr TestProxyRule) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) {
+ atomic.AddInt32(&socksProxiedRequests, 1)
+ return ctx, true
+}
diff --git a/plumbing/transport/ssh/internal/test/test_utils.go b/plumbing/transport/ssh/internal/test/test_utils.go
new file mode 100644
index 0000000..c3797b1
--- /dev/null
+++ b/plumbing/transport/ssh/internal/test/test_utils.go
@@ -0,0 +1,83 @@
+package test
+
+import (
+ "fmt"
+ "io"
+ "os/exec"
+ "runtime"
+ "strings"
+ "sync"
+
+ "github.com/gliderlabs/ssh"
+)
+
+func HandlerSSH(s ssh.Session) {
+ cmd, stdin, stderr, stdout, err := buildCommand(s.Command())
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ if err := cmd.Start(); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ go func() {
+ defer stdin.Close()
+ io.Copy(stdin, s)
+ }()
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ io.Copy(s.Stderr(), stderr)
+ }()
+
+ go func() {
+ defer wg.Done()
+ io.Copy(s, stdout)
+ }()
+
+ wg.Wait()
+
+ if err := cmd.Wait(); err != nil {
+ return
+ }
+
+}
+
+func buildCommand(c []string) (cmd *exec.Cmd, stdin io.WriteCloser, stderr, stdout io.ReadCloser, err error) {
+ if len(c) != 2 {
+ err = fmt.Errorf("invalid command")
+ return
+ }
+
+ // fix for Windows environments
+ var path string
+ if runtime.GOOS == "windows" {
+ path = strings.Replace(c[1], "/C:/", "C:/", 1)
+ } else {
+ path = c[1]
+ }
+
+ cmd = exec.Command(c[0], path)
+ stdout, err = cmd.StdoutPipe()
+ if err != nil {
+ return
+ }
+
+ stdin, err = cmd.StdinPipe()
+ if err != nil {
+ return
+ }
+
+ stderr, err = cmd.StderrPipe()
+ if err != nil {
+ return
+ }
+
+ return
+}
diff --git a/plumbing/transport/ssh/proxy_test.go b/plumbing/transport/ssh/proxy_test.go
index 3caf1ff..2fab851 100644
--- a/plumbing/transport/ssh/proxy_test.go
+++ b/plumbing/transport/ssh/proxy_test.go
@@ -1,36 +1,88 @@
package ssh
import (
+ "context"
"fmt"
+ "io/ioutil"
"log"
"net"
"os"
+ "sync/atomic"
"github.com/armon/go-socks5"
+ "github.com/gliderlabs/ssh"
+ "github.com/go-git/go-git/v5/plumbing/transport"
+ "github.com/go-git/go-git/v5/plumbing/transport/ssh/internal/test"
+
+ fixtures "github.com/go-git/go-git-fixtures/v4"
+ stdssh "golang.org/x/crypto/ssh"
. "gopkg.in/check.v1"
)
type ProxySuite struct {
- UploadPackSuite
+ u UploadPackSuite
+ fixtures.Suite
}
var _ = Suite(&ProxySuite{})
-func (s *ProxySuite) SetUpSuite(c *C) {
- s.UploadPackSuite.SetUpSuite(c)
+var socksProxiedRequests int32
- l, err := net.Listen("tcp", "localhost:0")
+func (s *ProxySuite) TestCommand(c *C) {
+ socksListener, err := net.Listen("tcp", "localhost:0")
c.Assert(err, IsNil)
- server, err := socks5.New(&socks5.Config{})
+ socksServer, err := socks5.New(&socks5.Config{
+ AuthMethods: []socks5.Authenticator{socks5.UserPassAuthenticator{
+ Credentials: socks5.StaticCredentials{
+ "user": "pass",
+ },
+ }},
+ Rules: TestProxyRule{},
+ })
c.Assert(err, IsNil)
+ go func() {
+ socksServer.Serve(socksListener)
+ }()
+ socksProxyAddr := fmt.Sprintf("socks5://localhost:%d", socksListener.Addr().(*net.TCPAddr).Port)
- port := l.Addr().(*net.TCPAddr).Port
-
- err = os.Setenv("ALL_PROXY", fmt.Sprintf("socks5://localhost:%d", port))
+ sshListener, err := net.Listen("tcp", "localhost:0")
c.Assert(err, IsNil)
-
+ sshServer := &ssh.Server{Handler: test.HandlerSSH}
go func() {
- log.Fatal(server.Serve(l))
+ log.Fatal(sshServer.Serve(sshListener))
}()
+
+ s.u.port = sshListener.Addr().(*net.TCPAddr).Port
+ s.u.base, err = ioutil.TempDir(os.TempDir(), fmt.Sprintf("go-git-ssh-%d", s.u.port))
+ c.Assert(err, IsNil)
+
+ DefaultAuthBuilder = func(user string) (AuthMethod, error) {
+ return &Password{User: user}, nil
+ }
+
+ ep := s.u.prepareRepository(c, fixtures.Basic().One(), "basic.git")
+ c.Assert(err, IsNil)
+ ep.Proxy = transport.ProxyOptions{
+ URL: socksProxyAddr,
+ Username: "user",
+ Password: "pass",
+ }
+
+ runner := runner{
+ config: &stdssh.ClientConfig{
+ HostKeyCallback: stdssh.InsecureIgnoreHostKey(),
+ },
+ }
+ _, err = runner.Command(transport.UploadPackServiceName, ep, nil)
+ c.Assert(err, IsNil)
+ proxyUsed := atomic.LoadInt32(&socksProxiedRequests) > 0
+ c.Assert(proxyUsed, Equals, true)
+}
+
+type TestProxyRule struct{}
+
+func (dr TestProxyRule) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) {
+ atomic.AddInt32(&socksProxiedRequests, 1)
+ return ctx, true
}
diff --git a/plumbing/transport/ssh/upload_pack_test.go b/plumbing/transport/ssh/upload_pack_test.go
index f172fee..fafff48 100644
--- a/plumbing/transport/ssh/upload_pack_test.go
+++ b/plumbing/transport/ssh/upload_pack_test.go
@@ -14,6 +14,7 @@ import (
"sync"
"github.com/go-git/go-git/v5/plumbing/transport"
+ testutils "github.com/go-git/go-git/v5/plumbing/transport/ssh/internal/test"
"github.com/go-git/go-git/v5/plumbing/transport/test"
"github.com/gliderlabs/ssh"
@@ -57,7 +58,7 @@ func (s *UploadPackSuite) SetUpSuite(c *C) {
s.UploadPackSuite.EmptyEndpoint = s.prepareRepository(c, fixtures.ByTag("empty").One(), "empty.git")
s.UploadPackSuite.NonExistentEndpoint = s.newEndpoint(c, "non-existent.git")
- server := &ssh.Server{Handler: handlerSSH}
+ server := &ssh.Server{Handler: testutils.HandlerSSH}
for _, opt := range s.opts {
opt(server)
}