From 479570f96957929bf71f756f6ef38d954b7741b8 Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Sat, 1 Jan 2022 15:56:25 -0600 Subject: [PATCH 1/7] unix to unix sockets over ssh added --- pkg/services/forwarder/ports.go | 214 ++++++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 23 deletions(-) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 50d50b880..0b5fc1973 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -6,16 +6,21 @@ import ( "errors" "fmt" "io" + "io/ioutil" "net" "net/http" + "net/url" + "os" "sort" "strconv" "strings" "sync" + "time" "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/google/tcpproxy" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" @@ -50,31 +55,142 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote return errors.New("proxy already running") } - split := strings.Split(remote, ":") - if len(split) != 2 { - return errors.New("invalid remote addr") - } - port, err := strconv.Atoi(split[1]) - if err != nil { - return err - } - address := tcpip.FullAddress{ - NIC: 1, - Addr: tcpip.Address(net.ParseIP(split[0]).To4()), - Port: uint16(port), - } - switch protocol { case types.UNIX: + // parse URI for remote + remoteUri, err := url.Parse(remote) + if err != nil { + return fmt.Errorf("failed to parse remote uri :%s : %w", remote, err) + } + + // build the address from remoteUri + remoteAddr := fmt.Sprintf("%s:%s", remoteUri.Hostname(), remoteUri.Port()) + + // dialFn opens remote connection for the proxy + var dialFn func(ctx context.Context, network, addr string) (conn net.Conn, e error) + + // dialFn is set based on the protocol provided by remoteUri.Scheme + switch remoteUri.Scheme { + case "ssh-tunnel": // unix-to-unix proxy (over SSH) + // query string to map for the remoteUri contains ssh config info + remoteQuery := remoteUri.Query() + + // username + sshuser := firstValueOrEmpty(remoteQuery["user"]) + if sshuser == "" { + return fmt.Errorf("user not provided for unix-ssh connection") + } + + // key + sshkeypath := firstValueOrEmpty(remoteQuery["key"]) + if sshkeypath == "" { + return fmt.Errorf("key not provided for unix-ssh connection") + } + + sshkeyBytes, err := ioutil.ReadFile(sshkeypath) + if err != nil { + return fmt.Errorf("failed to read ssh key: %s: %w", sshkeypath, err) + } + + // passphrase + passphrase := firstValueOrEmpty(remoteQuery["passphrase"]) + + var sshsigner ssh.Signer + + if passphrase == "" { + sshsigner, err = ssh.ParsePrivateKey(sshkeyBytes) + } else { + sshsigner, err = ssh.ParsePrivateKeyWithPassphrase(sshkeyBytes, []byte(passphrase)) + } + + // parse private key error? + if err != nil { + return fmt.Errorf("failed to parse ssh key: %s: %w", sshkeypath, err) + } + + // default ssh port if not set + if remoteUri.Port() == "" { + remoteAddr = fmt.Sprintf("%s:%s", remoteUri.Hostname(), "22") + } + + // build address + address, err := tcpipAddress(1, remoteAddr) + if err != nil { + return err + } + + // check the remoteUri path provided for nonsense + if remoteUri.Path == "" || remoteUri.Path == "/" { + return fmt.Errorf("remote uri must contain a path to a socket file") + } + + // the dialFn for unix-to-unix over SSH + dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + // underlying connection to endpoint for the ssh client + conn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) + if err != nil { + return nil, err + } + + // ssh client config that uses key authentication + config := &ssh.ClientConfig{ + User: sshuser, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(sshsigner), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + HostKeyAlgorithms: []string{ + ssh.KeyAlgoRSA, + ssh.KeyAlgoDSA, + ssh.KeyAlgoECDSA256, + ssh.KeyAlgoECDSA384, + ssh.KeyAlgoECDSA521, + ssh.KeyAlgoED25519, + }, + Timeout: 5 * time.Second, + } + + // get an sshConn using the underlying gonet.TCPConn + sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + + // build an ssh client using sshConn + sshClient := ssh.NewClient(sshConn, chans, reqs) + + // connection using sshclient's dialer + return sshClient.Dial("unix", remoteUri.Path) + } + + case "tcp": // unix-to-tcp proxy + // build address + address, err := tcpipAddress(1, remoteAddr) + if err != nil { + return err + } + + dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + return gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) + } + + default: + return fmt.Errorf("remote protocol for unix forwarder is not implemented: %s", remoteUri.Scheme) + } + + // build the tcp proxy var p tcpproxy.Proxy p.ListenFunc = func(_, socketPath string) (net.Listener, error) { + // remove existing socket file + if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { + return nil, err + } + return net.Listen("unix", socketPath) // override tcp to use unix socket } p.AddRoute(local, &tcpproxy.DialProxy{ - Addr: remote, - DialContext: func(ctx context.Context, network, addr string) (conn net.Conn, e error) { - return gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) - }, + Addr: remoteAddr, + DialContext: dialFn, }) if err := p.Start(); err != nil { return err @@ -91,7 +207,13 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote Remote: remote, underlying: &p, } + case types.UDP: + address, err := tcpipAddress(1, remote) + if err != nil { + return err + } + addr, err := net.ResolveUDPAddr("udp", local) if err != nil { return err @@ -114,6 +236,11 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote underlying: p, } case types.TCP: + address, err := tcpipAddress(1, remote) + if err != nil { + return err + } + var p tcpproxy.Proxy p.AddRoute(local, &tcpproxy.DialProxy{ Addr: remote, @@ -186,12 +313,21 @@ func (f *PortsForwarder) Mux() http.Handler { if req.Protocol == "" { req.Protocol = types.TCP } - remote, err := remote(req, r.RemoteAddr) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + + // contains unparsed remote field + remoteAddr := req.Remote + + // TCP and UDP rely on remote() to preparse the remote field + if req.Protocol != types.UNIX { + var err error + remoteAddr, err = remote(req, r.RemoteAddr) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } } - if err := f.Expose(req.Protocol, req.Local, remote); err != nil { + + if err := f.Expose(req.Protocol, req.Local, remoteAddr); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -234,3 +370,35 @@ func remote(req types.ExposeRequest, ip string) (string, error) { } return req.Remote, nil } + +// helper function for parsed URL query strings +func firstValueOrEmpty(x []string) string { + if len(x) > 0 { + return x[0] + } + return "" +} + +// helper function to build tcpip address +func tcpipAddress(nicId tcpip.NICID, remote string) (address tcpip.FullAddress, err error) { + + // build the address manual way + split := strings.Split(remote, ":") + if len(split) != 2 { + return address, errors.New("invalid remote addr") + } + + port, err := strconv.Atoi(split[1]) + if err != nil { + return address, err + + } + + address = tcpip.FullAddress{ + NIC: nicId, + Addr: tcpip.Address(net.ParseIP(split[0]).To4()), + Port: uint16(port), + } + + return address, err +} From feca58e9f99ce636f62648eb90f52f538c8560ef Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Wed, 5 Jan 2022 19:32:33 -0600 Subject: [PATCH 2/7] reuse sshclient connection --- pkg/services/forwarder/ports.go | 68 ++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 0b5fc1973..8e25784d9 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -124,41 +124,47 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote return fmt.Errorf("remote uri must contain a path to a socket file") } + // captured and used by dialFn + var sshClient *ssh.Client + // the dialFn for unix-to-unix over SSH dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { - // underlying connection to endpoint for the ssh client - conn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) - if err != nil { - return nil, err - } - - // ssh client config that uses key authentication - config := &ssh.ClientConfig{ - User: sshuser, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(sshsigner), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - HostKeyAlgorithms: []string{ - ssh.KeyAlgoRSA, - ssh.KeyAlgoDSA, - ssh.KeyAlgoECDSA256, - ssh.KeyAlgoECDSA384, - ssh.KeyAlgoECDSA521, - ssh.KeyAlgoED25519, - }, - Timeout: 5 * time.Second, + // create new sshClient not already done + if sshClient == nil { + // underlying connection to endpoint for the ssh client + conn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) + if err != nil { + return nil, err + } + + // ssh client config that uses key authentication + config := &ssh.ClientConfig{ + User: sshuser, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(sshsigner), + }, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + HostKeyAlgorithms: []string{ + ssh.KeyAlgoRSA, + ssh.KeyAlgoDSA, + ssh.KeyAlgoECDSA256, + ssh.KeyAlgoECDSA384, + ssh.KeyAlgoECDSA521, + ssh.KeyAlgoED25519, + }, + Timeout: 5 * time.Second, + } + + // get an sshConn using the underlying gonet.TCPConn + sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + + // build an ssh client using sshConn + sshClient = ssh.NewClient(sshConn, chans, reqs) } - // get an sshConn using the underlying gonet.TCPConn - sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) - if err != nil { - return nil, err - } - - // build an ssh client using sshConn - sshClient := ssh.NewClient(sshConn, chans, reqs) - // connection using sshclient's dialer return sshClient.Dial("unix", remoteUri.Path) } From ae2589bc0a5d8a8fee9ab129ada6214e2c620b32 Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Wed, 5 Jan 2022 19:32:56 -0600 Subject: [PATCH 3/7] tests for unix2unix and unix2tcp forwarders --- test/port_forwarding_test.go | 44 +++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/test/port_forwarding_test.go b/test/port_forwarding_test.go index 31f2dd841..1063ef403 100644 --- a/test/port_forwarding_test.go +++ b/test/port_forwarding_test.go @@ -2,6 +2,7 @@ package e2e import ( "context" + "fmt" "io" "net" "net/http" @@ -191,7 +192,7 @@ var _ = Describe("port forwarding", func() { unix2tcpfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) - out, err := sshExec(`curl http://gateway.containers.internal/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2tcpfwdsock + `","remote":"192.168.127.2:8080"}'`) + out, err := sshExec(`curl http://gateway.containers.internal/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2tcpfwdsock + `","remote":"tcp://192.168.127.2:8080"}'`) Expect(string(out)).Should(Equal("")) Expect(err).ShouldNot(HaveOccurred()) @@ -215,4 +216,45 @@ var _ = Describe("port forwarding", func() { g.Expect(resp.StatusCode).To(Equal(http.StatusOK)) }).Should(Succeed()) }) + + // unsure of if AF_UNIX socket support in windows just works this way; but windows seems to support AF_UNIX sockets + if runtime.GOOS != "windows" { + + It("should expose and reach rootless podman API using unix to unix forwarding over ssh", func() { + unix2unixfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) + + remoteuri := fmt.Sprintf(`ssh-tunnel://%s:%d%s?user=root&key=%s`, "192.168.127.2", 22, podmanSock, privateKeyFile) + _, err := sshExec(`curl http://192.168.127.1/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2unixfwdsock + `","remote":"` + remoteuri + `"}'`) + Expect(err).ShouldNot(HaveOccurred()) + + Eventually(func(g Gomega) { + sockfile, err := os.Stat(unix2unixfwdsock) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(sockfile.Mode().Type().String()).To(Equal(os.ModeSocket.String())) + }).Should(Succeed()) + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", unix2unixfwdsock) + }, + }, + } + + Eventually(func(g Gomega) { + resp, err := httpClient.Get("http://host/_ping") + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(resp.StatusCode).To(Equal(http.StatusOK)) + g.Expect(resp.ContentLength).To(Equal(int64(2))) + + reply := make([]byte, resp.ContentLength) + _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(string(reply)).To(Equal("OK")) + }).Should(Succeed()) + }) + + } + }) From 136fc2fb2ed32538b7c9a151e6e5faeb7f8d288f Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Wed, 5 Jan 2022 19:36:51 -0600 Subject: [PATCH 4/7] silence linter - nosec G106 --- pkg/services/forwarder/ports.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 8e25784d9..6c1182fe4 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -143,6 +143,7 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote Auth: []ssh.AuthMethod{ ssh.PublicKeys(sshsigner), }, + // #nosec G106 HostKeyCallback: ssh.InsecureIgnoreHostKey(), HostKeyAlgorithms: []string{ ssh.KeyAlgoRSA, From a5c284ca42d3fb14c5daa07222637b39322da94b Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Wed, 5 Jan 2022 19:40:05 -0600 Subject: [PATCH 5/7] appease linter var-naming convention --- pkg/services/forwarder/ports.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 6c1182fe4..95fccdcc0 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -58,22 +58,22 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote switch protocol { case types.UNIX: // parse URI for remote - remoteUri, err := url.Parse(remote) + remoteURI, err := url.Parse(remote) if err != nil { return fmt.Errorf("failed to parse remote uri :%s : %w", remote, err) } - // build the address from remoteUri - remoteAddr := fmt.Sprintf("%s:%s", remoteUri.Hostname(), remoteUri.Port()) + // build the address from remoteURI + remoteAddr := fmt.Sprintf("%s:%s", remoteURI.Hostname(), remoteURI.Port()) // dialFn opens remote connection for the proxy var dialFn func(ctx context.Context, network, addr string) (conn net.Conn, e error) - // dialFn is set based on the protocol provided by remoteUri.Scheme - switch remoteUri.Scheme { + // dialFn is set based on the protocol provided by remoteURI.Scheme + switch remoteURI.Scheme { case "ssh-tunnel": // unix-to-unix proxy (over SSH) - // query string to map for the remoteUri contains ssh config info - remoteQuery := remoteUri.Query() + // query string to map for the remoteURI contains ssh config info + remoteQuery := remoteURI.Query() // username sshuser := firstValueOrEmpty(remoteQuery["user"]) @@ -109,8 +109,8 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote } // default ssh port if not set - if remoteUri.Port() == "" { - remoteAddr = fmt.Sprintf("%s:%s", remoteUri.Hostname(), "22") + if remoteURI.Port() == "" { + remoteAddr = fmt.Sprintf("%s:%s", remoteURI.Hostname(), "22") } // build address @@ -119,8 +119,8 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote return err } - // check the remoteUri path provided for nonsense - if remoteUri.Path == "" || remoteUri.Path == "/" { + // check the remoteURI path provided for nonsense + if remoteURI.Path == "" || remoteURI.Path == "/" { return fmt.Errorf("remote uri must contain a path to a socket file") } @@ -167,7 +167,7 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote } // connection using sshclient's dialer - return sshClient.Dial("unix", remoteUri.Path) + return sshClient.Dial("unix", remoteURI.Path) } case "tcp": // unix-to-tcp proxy @@ -182,7 +182,7 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote } default: - return fmt.Errorf("remote protocol for unix forwarder is not implemented: %s", remoteUri.Scheme) + return fmt.Errorf("remote protocol for unix forwarder is not implemented: %s", remoteURI.Scheme) } // build the tcp proxy @@ -387,7 +387,7 @@ func firstValueOrEmpty(x []string) string { } // helper function to build tcpip address -func tcpipAddress(nicId tcpip.NICID, remote string) (address tcpip.FullAddress, err error) { +func tcpipAddress(nicID tcpip.NICID, remote string) (address tcpip.FullAddress, err error) { // build the address manual way split := strings.Split(remote, ":") @@ -402,7 +402,7 @@ func tcpipAddress(nicId tcpip.NICID, remote string) (address tcpip.FullAddress, } address = tcpip.FullAddress{ - NIC: nicId, + NIC: nicID, Addr: tcpip.Address(net.ParseIP(split[0]).To4()), Port: uint16(port), } From f64bf3769cfcb0d9331507efdaab086c3e2e0dbe Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Wed, 5 Jan 2022 20:12:39 -0600 Subject: [PATCH 6/7] fix that ensures sshClient reconnect in forwarder service --- pkg/services/forwarder/ports.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 95fccdcc0..7a8993bc2 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -125,14 +125,22 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote } // captured and used by dialFn + var tcpConn *gonet.TCPConn var sshClient *ssh.Client // the dialFn for unix-to-unix over SSH dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { - // create new sshClient not already done - if sshClient == nil { + // check underlying tcpConn to see if it's closed + if tcpConn != nil { + if _, err := tcpConn.Read(make([]byte, 0)); err == io.EOF { + tcpConn = nil + } + } + + // connect or reconnect to ssh + if tcpConn == nil || sshClient == nil { // underlying connection to endpoint for the ssh client - conn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) + tcpConn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) if err != nil { return nil, err } @@ -157,7 +165,7 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote } // get an sshConn using the underlying gonet.TCPConn - sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + sshConn, chans, reqs, err := ssh.NewClientConn(tcpConn, addr, config) if err != nil { return nil, err } From 6d76545f46e2654e1e2d912478b256ed31770bc1 Mon Sep 17 00:00:00 2001 From: Sam Peterson Date: Thu, 6 Jan 2022 09:16:37 -0600 Subject: [PATCH 7/7] connection locking for the sshClient in the port forwarding service --- pkg/services/forwarder/ports.go | 27 ++++++++++--- test/port_forwarding_test.go | 68 ++++++++++++++++----------------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/pkg/services/forwarder/ports.go b/pkg/services/forwarder/ports.go index 7a8993bc2..528fc7dfc 100644 --- a/pkg/services/forwarder/ports.go +++ b/pkg/services/forwarder/ports.go @@ -127,13 +127,19 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote // captured and used by dialFn var tcpConn *gonet.TCPConn var sshClient *ssh.Client + var connLock sync.Mutex + + // handles getting underlying ssh connection, having this outside of + // dialFn limits connLock to only the parts it's needed for in a way + // that doesn't get racy. + sshConnFn := func(ctx context.Context, network, addr string) (client *ssh.Client, err error) { + connLock.Lock() + defer connLock.Unlock() - // the dialFn for unix-to-unix over SSH - dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { // check underlying tcpConn to see if it's closed if tcpConn != nil { if _, err := tcpConn.Read(make([]byte, 0)); err == io.EOF { - tcpConn = nil + tcpConn = nil // set back to nil to force reconnect } } @@ -142,7 +148,7 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote // underlying connection to endpoint for the ssh client tcpConn, err := gonet.DialContextTCP(ctx, f.stack, address, ipv4.ProtocolNumber) if err != nil { - return nil, err + return sshClient, err } // ssh client config that uses key authentication @@ -167,13 +173,24 @@ func (f *PortsForwarder) Expose(protocol types.TransportProtocol, local, remote // get an sshConn using the underlying gonet.TCPConn sshConn, chans, reqs, err := ssh.NewClientConn(tcpConn, addr, config) if err != nil { - return nil, err + return sshClient, err } // build an ssh client using sshConn sshClient = ssh.NewClient(sshConn, chans, reqs) } + return sshClient, err + } + + // the dialFn for unix-to-unix over SSH + dialFn = func(ctx context.Context, network, addr string) (conn net.Conn, e error) { + // check or create new ssh connection + sshClient, err = sshConnFn(ctx, network, addr) + if err != nil { + return nil, err + } + // connection using sshclient's dialer return sshClient.Dial("unix", remoteURI.Path) } diff --git a/test/port_forwarding_test.go b/test/port_forwarding_test.go index 1063ef403..62c4408aa 100644 --- a/test/port_forwarding_test.go +++ b/test/port_forwarding_test.go @@ -217,44 +217,42 @@ var _ = Describe("port forwarding", func() { }).Should(Succeed()) }) - // unsure of if AF_UNIX socket support in windows just works this way; but windows seems to support AF_UNIX sockets - if runtime.GOOS != "windows" { - - It("should expose and reach rootless podman API using unix to unix forwarding over ssh", func() { - unix2unixfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) - - remoteuri := fmt.Sprintf(`ssh-tunnel://%s:%d%s?user=root&key=%s`, "192.168.127.2", 22, podmanSock, privateKeyFile) - _, err := sshExec(`curl http://192.168.127.1/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2unixfwdsock + `","remote":"` + remoteuri + `"}'`) - Expect(err).ShouldNot(HaveOccurred()) - - Eventually(func(g Gomega) { - sockfile, err := os.Stat(unix2unixfwdsock) - g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(sockfile.Mode().Type().String()).To(Equal(os.ModeSocket.String())) - }).Should(Succeed()) - - httpClient := &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return net.Dial("unix", unix2unixfwdsock) - }, - }, - } + It("should expose and reach rootless podman API using unix to unix forwarding over ssh", func() { + if runtime.GOOS == "windows" { + Skip("AF_UNIX not supported on Windows") + } - Eventually(func(g Gomega) { - resp, err := httpClient.Get("http://host/_ping") - g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(resp.StatusCode).To(Equal(http.StatusOK)) - g.Expect(resp.ContentLength).To(Equal(int64(2))) + unix2unixfwdsock, _ := filepath.Abs(filepath.Join(tmpDir, "podman-unix-to-unix-forwarding.sock")) - reply := make([]byte, resp.ContentLength) - _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + remoteuri := fmt.Sprintf(`ssh-tunnel://%s:%d%s?user=root&key=%s`, "192.168.127.2", 22, podmanSock, privateKeyFile) + _, err := sshExec(`curl http://192.168.127.1/services/forwarder/expose -X POST -d'{"protocol":"unix","local":"` + unix2unixfwdsock + `","remote":"` + remoteuri + `"}'`) + Expect(err).ShouldNot(HaveOccurred()) - g.Expect(err).ShouldNot(HaveOccurred()) - g.Expect(string(reply)).To(Equal("OK")) - }).Should(Succeed()) - }) + Eventually(func(g Gomega) { + sockfile, err := os.Stat(unix2unixfwdsock) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(sockfile.Mode().Type().String()).To(Equal(os.ModeSocket.String())) + }).Should(Succeed()) + + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", unix2unixfwdsock) + }, + }, + } - } + Eventually(func(g Gomega) { + resp, err := httpClient.Get("http://host/_ping") + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(resp.StatusCode).To(Equal(http.StatusOK)) + g.Expect(resp.ContentLength).To(Equal(int64(2))) + reply := make([]byte, resp.ContentLength) + _, err = io.ReadAtLeast(resp.Body, reply, len(reply)) + + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(string(reply)).To(Equal("OK")) + }).Should(Succeed()) + }) })