From 914a486af6cde1c9e06c76c27b596b9658fbc18c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 29 Jan 2021 14:32:56 -0800 Subject: [PATCH] safesocket: refactor macOS auth code, pull out separate LocalTCPPortAndToken --- safesocket/basic_test.go | 2 +- safesocket/safesocket.go | 20 +++++++++++++ safesocket/safesocket_darwin.go | 52 +++++++++++++++++++++++++++++++++ safesocket/safesocket_test.go | 13 +++++++++ safesocket/unixsocket.go | 40 +++++++------------------ 5 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 safesocket/safesocket_darwin.go create mode 100644 safesocket/safesocket_test.go diff --git a/safesocket/basic_test.go b/safesocket/basic_test.go index 23727fc4f..863367111 100644 --- a/safesocket/basic_test.go +++ b/safesocket/basic_test.go @@ -32,7 +32,7 @@ func TestBasics(t *testing.T) { errs <- err return } - fmt.Printf("server read %d bytes.\n", n) + t.Logf("server read %d bytes.", n) if string(b[:n]) != "world" { errs <- fmt.Errorf("got %#v, expected %#v\n", string(b[:n]), "world") return diff --git a/safesocket/safesocket.go b/safesocket/safesocket.go index 3fbacc4a2..19e183463 100644 --- a/safesocket/safesocket.go +++ b/safesocket/safesocket.go @@ -7,7 +7,9 @@ package safesocket import ( + "errors" "net" + "runtime" ) type closeable interface { @@ -43,3 +45,21 @@ func Connect(path string, port uint16) (net.Conn, error) { func Listen(path string, port uint16) (_ net.Listener, gotPort uint16, _ error) { return listen(path, port) } + +var ( + ErrTokenNotFound = errors.New("no token found") + ErrNoTokenOnOS = errors.New("no token on " + runtime.GOOS) +) + +var localTCPPortAndToken func() (port int, token string, err error) + +// LocalTCPPortAndToken returns the port number and auth token to connect to +// the local Tailscale daemon. It's currently only applicable on macOS +// when tailscaled is being run in the Mac Sandbox from the App Store version +// of Tailscale. +func LocalTCPPortAndToken() (port int, token string, err error) { + if localTCPPortAndToken == nil { + return 0, "", ErrNoTokenOnOS + } + return localTCPPortAndToken() +} diff --git a/safesocket/safesocket_darwin.go b/safesocket/safesocket_darwin.go new file mode 100644 index 000000000..c0d43c41a --- /dev/null +++ b/safesocket/safesocket_darwin.go @@ -0,0 +1,52 @@ +// Copyright (c) 2021 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 safesocket + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "strconv" + "strings" +) + +func init() { + localTCPPortAndToken = localTCPPortAndTokenDarwin +} + +func localTCPPortAndTokenDarwin() (port int, token string, err error) { + out, err := exec.Command("lsof", + "-n", // numeric sockets; don't do DNS lookups, etc + "-a", // logical AND remaining options + fmt.Sprintf("-u%d", os.Getuid()), // process of same user only + "-c", "IPNExtension", // starting with IPNExtension + "-F", // machine-readable output + ).Output() + if err != nil { + return 0, "", fmt.Errorf("failed to run lsof looking for IPNExtension: %w", err) + } + bs := bufio.NewScanner(bytes.NewReader(out)) + subStr := []byte(".tailscale.ipn.macos/sameuserproof-") + for bs.Scan() { + line := bs.Bytes() + i := bytes.Index(line, subStr) + if i == -1 { + continue + } + f := strings.SplitN(string(line[i+len(subStr):]), "-", 2) + if len(f) != 2 { + continue + } + portStr, token := f[0], f[1] + port, err := strconv.Atoi(portStr) + if err != nil { + return 0, "", fmt.Errorf("invalid port %q found in lsof", portStr) + } + return port, token, nil + } + return 0, "", ErrTokenNotFound +} diff --git a/safesocket/safesocket_test.go b/safesocket/safesocket_test.go new file mode 100644 index 000000000..4b39c11cd --- /dev/null +++ b/safesocket/safesocket_test.go @@ -0,0 +1,13 @@ +// Copyright (c) 2021 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 safesocket + +import "testing" + +func TestLocalTCPPortAndToken(t *testing.T) { + // Just test that it compiles for now (is available on all platforms). + port, token, err := LocalTCPPortAndToken() + t.Logf("got %v, %s, %v", port, token, err) +} diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index 31322dd3c..fd2a58852 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -7,17 +7,15 @@ package safesocket import ( - "bufio" - "bytes" "fmt" "io" "io/ioutil" "log" "net" "os" - "os/exec" "path/filepath" "runtime" + "strconv" "strings" ) @@ -166,42 +164,24 @@ func connectMacOSAppSandbox() (net.Conn, error) { } f := strings.SplitN(best.Name(), "-", 3) portStr, token := f[1], f[2] - return connectMacTCP(portStr, token) + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("invalid port %q", portStr) + } + return connectMacTCP(port, token) } // Otherwise, assume we're running the cmd/tailscale binary from outside the // App Sandbox. - - out, err := exec.Command("lsof", - "-n", // numeric sockets; don't do DNS lookups, etc - "-a", // logical AND remaining options - fmt.Sprintf("-u%d", os.Getuid()), // process of same user only - "-c", "IPNExtension", // starting with IPNExtension - "-F", // machine-readable output - ).Output() + port, token, err := LocalTCPPortAndToken() if err != nil { return nil, err } - bs := bufio.NewScanner(bytes.NewReader(out)) - subStr := []byte(".tailscale.ipn.macos/sameuserproof-") - for bs.Scan() { - line := bs.Bytes() - i := bytes.Index(line, subStr) - if i == -1 { - continue - } - f := strings.SplitN(string(line[i+len(subStr):]), "-", 2) - if len(f) != 2 { - continue - } - portStr, token := f[0], f[1] - return connectMacTCP(portStr, token) - } - return nil, fmt.Errorf("failed to find Tailscale's IPNExtension process") + return connectMacTCP(port, token) } -func connectMacTCP(portStr, token string) (net.Conn, error) { - c, err := net.Dial("tcp", "localhost:"+portStr) +func connectMacTCP(port int, token string) (net.Conn, error) { + c, err := net.Dial("tcp", "localhost:"+strconv.Itoa(port)) if err != nil { return nil, fmt.Errorf("error dialing IPNExtension: %w", err) }