From 38243c6a55e10bc7a97f6c58dc57d644dc378ab2 Mon Sep 17 00:00:00 2001 From: "Jason T. Greene" Date: Wed, 20 Oct 2021 22:23:21 -0500 Subject: [PATCH] Add support for multiple unix socket forwards over ssh --- cmd/gvproxy/main.go | 51 +++++++++++++++--------------------- cmd/gvproxy/ssh_forwarder.go | 4 +-- test/ignition.go | 7 +++++ test/port_forwarding_test.go | 25 +++++++++++++++++- test/suite_test.go | 35 +++++++++++++++---------- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/cmd/gvproxy/main.go b/cmd/gvproxy/main.go index 51a780094..b0a77be83 100644 --- a/cmd/gvproxy/main.go +++ b/cmd/gvproxy/main.go @@ -32,10 +32,10 @@ var ( endpoints arrayFlags vpnkitSocket string qemuSocket string - forwardSocket string - forwardDest string - forwardUser string - forwardIdentify string + forwardSocket arrayFlags + forwardDest arrayFlags + forwardUser arrayFlags + forwardIdentify arrayFlags sshPort int pidFile string exitCode int @@ -53,10 +53,10 @@ func main() { flag.IntVar(&sshPort, "ssh-port", 2222, "Port to access the guest virtual machine. Must be between 1024 and 65535") flag.StringVar(&vpnkitSocket, "listen-vpnkit", "", "VPNKit socket to be used by Hyperkit") flag.StringVar(&qemuSocket, "listen-qemu", "", "Socket to be used by Qemu") - flag.StringVar(&forwardSocket, "forward-sock", "", "Forwards a unix socket to the guest virtual machine over SSH") - flag.StringVar(&forwardDest, "forward-dest", "", "Forwards a unix socket to the guest virtual machine over SSH") - flag.StringVar(&forwardUser, "forward-user", "", "SSH user to use for unix socket forward") - flag.StringVar(&forwardIdentify, "forward-identity", "", "Path to SSH identity key for forwarding") + flag.Var(&forwardSocket, "forward-sock", "Forwards a unix socket to the guest virtual machine over SSH") + flag.Var(&forwardDest, "forward-dest", "Forwards a unix socket to the guest virtual machine over SSH") + flag.Var(&forwardUser, "forward-user", "SSH user to use for unix socket forward") + flag.Var(&forwardIdentify, "forward-identity", "Path to SSH identity key for forwarding") flag.StringVar(&pidFile, "pid-file", "", "Generate a file with the PID in it") flag.Parse() ctx, cancel := context.WithCancel(context.Background()) @@ -95,26 +95,16 @@ func main() { protocol = types.QemuProtocol } - forwardCount := 0 - if forwardDest != "" { - forwardCount++ + if c := len(forwardSocket); c != len(forwardDest) || c != len(forwardUser) || c != len(forwardIdentify) { + exitWithError(errors.New("-forward-sock, --forward-dest, --forward-user, and --forward-identity must all be specified together, " + + "the same number of times, or not at all")) } - if forwardIdentify != "" { - _, err := os.Stat(forwardIdentify) + + for i := 0; i < len(forwardSocket); i++ { + _, err := os.Stat(forwardIdentify[i]) if err != nil { - exitWithError(errors.Wrapf(err, "Identity file %s can't be loaded", forwardIdentify)) + exitWithError(errors.Wrapf(err, "Identity file %s can't be loaded", forwardIdentify[i])) } - forwardCount++ - } - if forwardUser != "" { - forwardCount++ - } - if forwardSocket != "" { - forwardCount++ - } - - if forwardCount > 0 && forwardCount < 4 { - exitWithError(errors.New("-forward-sock, --forward-dest, --forward-user, and --forward-identity must all be specified together, or none specified")) } // Create a PID file if requested @@ -319,16 +309,17 @@ func run(ctx context.Context, g *errgroup.Group, configuration *types.Configurat }) } - if forwardSocket != "" { + for i := 0; i < len(forwardSocket); i++ { dest := url.URL{ Scheme: "ssh", - User: url.User(forwardUser), + User: url.User(forwardUser[i]), Host: sshHostPort, - Path: forwardDest, + Path: forwardDest[i], } + j := i g.Go(func() error { - defer os.Remove(forwardSocket) - forward, err := CreateSSHForward(ctx, forwardSocket, dest, forwardIdentify, vn) + defer os.Remove(forwardSocket[j]) + forward, err := CreateSSHForward(ctx, forwardSocket[j], dest, forwardIdentify[j], vn) if err != nil { return err } diff --git a/cmd/gvproxy/ssh_forwarder.go b/cmd/gvproxy/ssh_forwarder.go index 4e6fdb1ac..a6ba67ab4 100644 --- a/cmd/gvproxy/ssh_forwarder.go +++ b/cmd/gvproxy/ssh_forwarder.go @@ -100,8 +100,6 @@ func setupProxy(ctx context.Context, socketURI *url.URL, dest *url.URL, identity return &SSHForward{}, err } - logrus.Infof("Socket forward listening on: %s\n", socketURI) - connectFunc := func(bastion *sshclient.Bastion) (net.Conn, error) { timeout := 5 * time.Second if bastion != nil { @@ -126,7 +124,7 @@ func setupProxy(ctx context.Context, socketURI *url.URL, dest *url.URL, identity return &SSHForward{}, err } - logrus.Infof("SSH Bastion connected: %s\n", dest) + logrus.Infof("Socket forward established: %s to %s\n", socketURI.Path, dest.Path) return &SSHForward{listener, &bastion, socketURI}, nil } diff --git a/test/ignition.go b/test/ignition.go index 3bb836a17..523d804d3 100644 --- a/test/ignition.go +++ b/test/ignition.go @@ -51,6 +51,13 @@ ExecStart=/usr/bin/sleep infinity "sudo", }, }, + { + Name: "root", + PasswordHash: &password, + SSHAuthorizedKeys: []SSHAuthorizedKey{ + SSHAuthorizedKey(publicKey), + }, + }, }, } diff --git a/test/port_forwarding_test.go b/test/port_forwarding_test.go index 62d45edd1..27fbbc635 100644 --- a/test/port_forwarding_test.go +++ b/test/port_forwarding_test.go @@ -176,7 +176,7 @@ address=/foobar/1.2.3.4 }).Should(Succeed()) }) - It("should reach podman API using unix socket forwarding over ssh", func() { + It("should reach rootless podman API using unix socket forwarding over ssh", func() { httpClient := &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -198,4 +198,27 @@ address=/foobar/1.2.3.4 g.Expect(string(reply)).To(Equal("OK")) }).Should(Succeed()) }) + + It("should reach rootful podman API using unix socket forwarding over ssh", func() { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", forwardRootSock) + }, + }, + } + + 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()) + }) }) diff --git a/test/suite_test.go b/test/suite_test.go index c85389bf6..02b3718f0 100644 --- a/test/suite_test.go +++ b/test/suite_test.go @@ -26,26 +26,28 @@ func TestSuite(t *testing.T) { } const ( - sock = "/tmp/gvproxy-api.sock" - qemuPort = 5555 - sshPort = 2222 - ignitionUser = "test" - qconLog = "qcon.log" - podmanSock = "/run/user/1001/podman/podman.sock" + sock = "/tmp/gvproxy-api.sock" + qemuPort = 5555 + sshPort = 2222 + ignitionUser = "test" + qconLog = "qcon.log" + podmanSock = "/run/user/1001/podman/podman.sock" + podmanRootSock = "/run/podman/podman.sock" // #nosec "test" (for manual usage) ignitionPasswordHash = "$y$j9T$TqJWt3/mKJbH0sYi6B/LD1$QjVRuUgntjTHjAdAkqhkr4F73m.Be4jBXdAaKw98sPC" ) var ( - tmpDir string - binDir string - host *exec.Cmd - client *exec.Cmd - privateKeyFile string - publicKeyFile string - ignFile string - forwardSock string + tmpDir string + binDir string + host *exec.Cmd + client *exec.Cmd + privateKeyFile string + publicKeyFile string + ignFile string + forwardSock string + forwardRootSock string ) func init() { @@ -55,6 +57,8 @@ func init() { publicKeyFile = privateKeyFile + ".pub" ignFile = filepath.Join(tmpDir, "test.ign") forwardSock = filepath.Join(tmpDir, "podman-remote.sock") + forwardRootSock = filepath.Join(tmpDir, "podman-root-remote.sock") + } var _ = BeforeSuite(func() { @@ -78,7 +82,10 @@ outer: // #nosec host = exec.Command(filepath.Join(binDir, "gvproxy"), fmt.Sprintf("--listen=unix://%s", sock), fmt.Sprintf("--listen-qemu=tcp://127.0.0.1:%d", qemuPort), fmt.Sprintf("--forward-sock=%s", forwardSock), fmt.Sprintf("--forward-dest=%s", podmanSock), fmt.Sprintf("--forward-user=%s", ignitionUser), + fmt.Sprintf("--forward-identity=%s", privateKeyFile), + fmt.Sprintf("--forward-sock=%s", forwardRootSock), fmt.Sprintf("--forward-dest=%s", podmanRootSock), fmt.Sprintf("--forward-user=%s", "root"), fmt.Sprintf("--forward-identity=%s", privateKeyFile)) + host.Stderr = os.Stderr host.Stdout = os.Stdout Expect(host.Start()).Should(Succeed())