From 8294915780797bcff07cdf531554e0c973fb4aab Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 24 Mar 2022 12:22:36 -0700 Subject: [PATCH] cmd/tailscale/cli: add start of 'ssh' subcommand Updates #3802 Change-Id: Iabc07c00c7e4f43944cfe7daec8d2b66ac002289 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 1 + cmd/tailscale/cli/ssh.go | 97 ++++++++++++++++++++++++++++++++++++++ cmd/tailscale/depaware.txt | 3 +- go.mod | 1 + go.sum | 2 + 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 cmd/tailscale/cli/ssh.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 5b869ec7a..0b5995c7d 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -171,6 +171,7 @@ change in the future. statusCmd, pingCmd, ncCmd, + sshCmd, versionCmd, webCmd, fileCmd, diff --git a/cmd/tailscale/cli/ssh.go b/cmd/tailscale/cli/ssh.go new file mode 100644 index 000000000..2f96fd95d --- /dev/null +++ b/cmd/tailscale/cli/ssh.go @@ -0,0 +1,97 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cli + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "os/user" + "runtime" + "strings" + "syscall" + + "github.com/alessio/shellescape" + "github.com/peterbourgon/ff/v3/ffcli" + "tailscale.com/envknob" +) + +var sshCmd = &ffcli.Command{ + Name: "ssh", + ShortUsage: "ssh [user@] [args...]", + ShortHelp: "SSH to a Tailscale machine", + Exec: runSSH, +} + +func runSSH(ctx context.Context, args []string) error { + if len(args) == 0 { + return errors.New("usage: ssh [user@]") + } + arg, argRest := args[0], args[1:] + username, host, ok := strings.Cut(arg, "@") + if !ok { + host = arg + lu, err := user.Current() + if err != nil { + return nil + } + username = lu.Username + } + ssh, err := exec.LookPath("ssh") + if err != nil { + // TODO(bradfitz): use Go's crypto/ssh client instead + // of failing. But for now: + return fmt.Errorf("no system 'ssh' command found: %w", err) + } + tailscaleBin, err := os.Executable() + if err != nil { + return err + } + argv := append([]string{ + ssh, + + "-o", fmt.Sprintf("ProxyCommand %s --socket=%s nc %%h %%p", + shellescape.Quote(tailscaleBin), + shellescape.Quote(rootArgs.socket), + ), + + // Explicitly rebuild the user@host argument rather than + // passing it through. In general, the use of OpenSSH's ssh + // binary is a crutch for now. We don't want to be + // Hyrum-locked into passing through all OpenSSH flags to the + // OpenSSH client forever. We try to make our flags and args + // be compatible, but only a subset. The "tailscale ssh" + // command should be a simple and portable one. If they want + // to use a different one, we'll later be making stock ssh + // work well by default too. (doing things like automatically + // setting known_hosts, etc) + username + "@" + host, + }, argRest...) + + if runtime.GOOS == "windows" { + // Don't use syscall.Exec on Windows. + cmd := exec.Command(ssh, argv[1:]...) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + var ee *exec.ExitError + err := cmd.Run() + if errors.As(err, &ee) { + os.Exit(ee.ExitCode()) + } + return err + } + + if envknob.Bool("TS_DEBUG_SSH_EXEC") { + log.Printf("Running: %q, %q ...", ssh, argv) + } + if err := syscall.Exec(ssh, argv, os.Environ()); err != nil { + return err + } + return errors.New("unreachable") +} diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 57004663a..f80bea3b2 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -1,5 +1,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware) + github.com/alessio/shellescape from tailscale.com/cmd/tailscale/cli W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+ W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy @@ -193,7 +194,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep os from crypto/rand+ os/exec from github.com/toqueteos/webbrowser+ os/signal from tailscale.com/cmd/tailscale/cli - os/user from tailscale.com/util/groupmember + os/user from tailscale.com/util/groupmember+ path from html/template+ path/filepath from crypto/x509+ reflect from crypto/x509+ diff --git a/go.mod b/go.mod index d37cbf234..2fe4aefbe 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( filippo.io/mkcert v1.4.3 github.com/akutz/memconn v0.1.0 + github.com/alessio/shellescape v1.4.1 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 github.com/aws/aws-sdk-go-v2 v1.11.2 github.com/aws/aws-sdk-go-v2/config v1.11.0 diff --git a/go.sum b/go.sum index c90f1c2f6..5548c874b 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=