From 2531065d1010424866ef764bbea3aa3708cb49b2 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 9 Oct 2024 18:06:56 -0700 Subject: [PATCH] clientupdate, ipn/localapi: don't use google/uuid, thin iOS deps We were using google/uuid in two places and that brought in database/sql/driver. We didn't need it in either place. Updates #13760 Updates tailscale/corp#20099 Change-Id: Ieed32f1bebe35d35f47ec5a2a429268f24f11f1f Signed-off-by: Brad Fitzpatrick --- clientupdate/clientupdate.go | 199 ----------------------- clientupdate/clientupdate_notwindows.go | 10 ++ clientupdate/clientupdate_windows.go | 205 +++++++++++++++++++++++- cmd/tailscale/depaware.txt | 6 +- cmd/tailscaled/depaware.txt | 4 +- ipn/localapi/localapi.go | 3 +- tstest/iosdeps/iosdeps_test.go | 2 + 7 files changed, 219 insertions(+), 210 deletions(-) create mode 100644 clientupdate/clientupdate_notwindows.go diff --git a/clientupdate/clientupdate.go b/clientupdate/clientupdate.go index 67edce05b..dbca58722 100644 --- a/clientupdate/clientupdate.go +++ b/clientupdate/clientupdate.go @@ -27,11 +27,9 @@ "strconv" "strings" - "github.com/google/uuid" "tailscale.com/clientupdate/distsign" "tailscale.com/types/logger" "tailscale.com/util/cmpver" - "tailscale.com/util/winutil" "tailscale.com/version" "tailscale.com/version/distro" ) @@ -756,164 +754,6 @@ func (up *Updater) updateMacAppStore() error { return nil } -const ( - // winMSIEnv is the environment variable that, if set, is the MSI file for - // the update command to install. It's passed like this so we can stop the - // tailscale.exe process from running before the msiexec process runs and - // tries to overwrite ourselves. - winMSIEnv = "TS_UPDATE_WIN_MSI" - // winExePathEnv is the environment variable that is set along with - // winMSIEnv and carries the full path of the calling tailscale.exe binary. - // It is used to re-launch the GUI process (tailscale-ipn.exe) after - // install is complete. - winExePathEnv = "TS_UPDATE_WIN_EXE_PATH" -) - -var ( - verifyAuthenticode func(string) error // set non-nil only on Windows - markTempFileFunc func(string) error // set non-nil only on Windows -) - -func (up *Updater) updateWindows() error { - if msi := os.Getenv(winMSIEnv); msi != "" { - // stdout/stderr from this part of the install could be lost since the - // parent tailscaled is replaced. Create a temp log file to have some - // output to debug with in case update fails. - close, err := up.switchOutputToFile() - if err != nil { - up.Logf("failed to create log file for installation: %v; proceeding with existing outputs", err) - } else { - defer close.Close() - } - - up.Logf("installing %v ...", msi) - if err := up.installMSI(msi); err != nil { - up.Logf("MSI install failed: %v", err) - return err - } - - up.Logf("success.") - return nil - } - - if !winutil.IsCurrentProcessElevated() { - return errors.New(`update must be run as Administrator - -you can run the command prompt as Administrator one of these ways: -* right-click cmd.exe, select 'Run as administrator' -* press Windows+x, then press a -* press Windows+r, type in "cmd", then press Ctrl+Shift+Enter`) - } - ver, err := requestedTailscaleVersion(up.Version, up.Track) - if err != nil { - return err - } - arch := runtime.GOARCH - if arch == "386" { - arch = "x86" - } - if !up.confirm(ver) { - return nil - } - - tsDir := filepath.Join(os.Getenv("ProgramData"), "Tailscale") - msiDir := filepath.Join(tsDir, "MSICache") - if fi, err := os.Stat(tsDir); err != nil { - return fmt.Errorf("expected %s to exist, got stat error: %w", tsDir, err) - } else if !fi.IsDir() { - return fmt.Errorf("expected %s to be a directory; got %v", tsDir, fi.Mode()) - } - if err := os.MkdirAll(msiDir, 0700); err != nil { - return err - } - up.cleanupOldDownloads(filepath.Join(msiDir, "*.msi")) - pkgsPath := fmt.Sprintf("%s/tailscale-setup-%s-%s.msi", up.Track, ver, arch) - msiTarget := filepath.Join(msiDir, path.Base(pkgsPath)) - if err := up.downloadURLToFile(pkgsPath, msiTarget); err != nil { - return err - } - - up.Logf("verifying MSI authenticode...") - if err := verifyAuthenticode(msiTarget); err != nil { - return fmt.Errorf("authenticode verification of %s failed: %w", msiTarget, err) - } - up.Logf("authenticode verification succeeded") - - up.Logf("making tailscale.exe copy to switch to...") - up.cleanupOldDownloads(filepath.Join(os.TempDir(), "tailscale-updater-*.exe")) - selfOrig, selfCopy, err := makeSelfCopy() - if err != nil { - return err - } - defer os.Remove(selfCopy) - up.Logf("running tailscale.exe copy for final install...") - - cmd := exec.Command(selfCopy, "update") - cmd.Env = append(os.Environ(), winMSIEnv+"="+msiTarget, winExePathEnv+"="+selfOrig) - cmd.Stdout = up.Stderr - cmd.Stderr = up.Stderr - cmd.Stdin = os.Stdin - if err := cmd.Start(); err != nil { - return err - } - // Once it's started, exit ourselves, so the binary is free - // to be replaced. - os.Exit(0) - panic("unreachable") -} - -func (up *Updater) switchOutputToFile() (io.Closer, error) { - var logFilePath string - exePath, err := os.Executable() - if err != nil { - logFilePath = filepath.Join(os.TempDir(), "tailscale-updater.log") - } else { - logFilePath = strings.TrimSuffix(exePath, ".exe") + ".log" - } - - up.Logf("writing update output to %q", logFilePath) - logFile, err := os.Create(logFilePath) - if err != nil { - return nil, err - } - - up.Logf = func(m string, args ...any) { - fmt.Fprintf(logFile, m+"\n", args...) - } - up.Stdout = logFile - up.Stderr = logFile - return logFile, nil -} - -func (up *Updater) installMSI(msi string) error { - var err error - for tries := 0; tries < 2; tries++ { - cmd := exec.Command("msiexec.exe", "/i", filepath.Base(msi), "/quiet", "/norestart", "/qn") - cmd.Dir = filepath.Dir(msi) - cmd.Stdout = up.Stdout - cmd.Stderr = up.Stderr - cmd.Stdin = os.Stdin - err = cmd.Run() - if err == nil { - break - } - up.Logf("Install attempt failed: %v", err) - uninstallVersion := up.currentVersion - if v := os.Getenv("TS_DEBUG_UNINSTALL_VERSION"); v != "" { - uninstallVersion = v - } - // Assume it's a downgrade, which msiexec won't permit. Uninstall our current version first. - up.Logf("Uninstalling current version %q for downgrade...", uninstallVersion) - cmd = exec.Command("msiexec.exe", "/x", msiUUIDForVersion(uninstallVersion), "/norestart", "/qn") - cmd.Stdout = up.Stdout - cmd.Stderr = up.Stderr - cmd.Stdin = os.Stdin - err = cmd.Run() - up.Logf("msiexec uninstall: %v", err) - } - return err -} - // cleanupOldDownloads removes all files matching glob (see filepath.Glob). // Only regular files are removed, so the glob must match specific files and // not directories. @@ -938,45 +778,6 @@ func (up *Updater) cleanupOldDownloads(glob string) { } } -func msiUUIDForVersion(ver string) string { - arch := runtime.GOARCH - if arch == "386" { - arch = "x86" - } - track, err := versionToTrack(ver) - if err != nil { - track = UnstableTrack - } - msiURL := fmt.Sprintf("https://pkgs.tailscale.com/%s/tailscale-setup-%s-%s.msi", track, ver, arch) - return "{" + strings.ToUpper(uuid.NewSHA1(uuid.NameSpaceURL, []byte(msiURL)).String()) + "}" -} - -func makeSelfCopy() (origPathExe, tmpPathExe string, err error) { - selfExe, err := os.Executable() - if err != nil { - return "", "", err - } - f, err := os.Open(selfExe) - if err != nil { - return "", "", err - } - defer f.Close() - f2, err := os.CreateTemp("", "tailscale-updater-*.exe") - if err != nil { - return "", "", err - } - if f := markTempFileFunc; f != nil { - if err := f(f2.Name()); err != nil { - return "", "", err - } - } - if _, err := io.Copy(f2, f); err != nil { - f2.Close() - return "", "", err - } - return selfExe, f2.Name(), f2.Close() -} - func (up *Updater) downloadURLToFile(pathSrc, fileDst string) (ret error) { c, err := distsign.NewClient(up.Logf, up.PkgsAddr) if err != nil { diff --git a/clientupdate/clientupdate_notwindows.go b/clientupdate/clientupdate_notwindows.go new file mode 100644 index 000000000..edadc210c --- /dev/null +++ b/clientupdate/clientupdate_notwindows.go @@ -0,0 +1,10 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +//go:build !windows + +package clientupdate + +func (up *Updater) updateWindows() error { + panic("unreachable") +} diff --git a/clientupdate/clientupdate_windows.go b/clientupdate/clientupdate_windows.go index 2f6899a60..973722974 100644 --- a/clientupdate/clientupdate_windows.go +++ b/clientupdate/clientupdate_windows.go @@ -7,13 +7,57 @@ package clientupdate import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strings" + + "github.com/google/uuid" "golang.org/x/sys/windows" + "tailscale.com/util/winutil" "tailscale.com/util/winutil/authenticode" ) -func init() { - markTempFileFunc = markTempFileWindows - verifyAuthenticode = verifyTailscale +const ( + // winMSIEnv is the environment variable that, if set, is the MSI file for + // the update command to install. It's passed like this so we can stop the + // tailscale.exe process from running before the msiexec process runs and + // tries to overwrite ourselves. + winMSIEnv = "TS_UPDATE_WIN_MSI" + // winExePathEnv is the environment variable that is set along with + // winMSIEnv and carries the full path of the calling tailscale.exe binary. + // It is used to re-launch the GUI process (tailscale-ipn.exe) after + // install is complete. + winExePathEnv = "TS_UPDATE_WIN_EXE_PATH" +) + +func makeSelfCopy() (origPathExe, tmpPathExe string, err error) { + selfExe, err := os.Executable() + if err != nil { + return "", "", err + } + f, err := os.Open(selfExe) + if err != nil { + return "", "", err + } + defer f.Close() + f2, err := os.CreateTemp("", "tailscale-updater-*.exe") + if err != nil { + return "", "", err + } + if err := markTempFileWindows(f2.Name()); err != nil { + return "", "", err + } + if _, err := io.Copy(f2, f); err != nil { + f2.Close() + return "", "", err + } + return selfExe, f2.Name(), f2.Close() } func markTempFileWindows(name string) error { @@ -23,6 +67,159 @@ func markTempFileWindows(name string) error { const certSubjectTailscale = "Tailscale Inc." -func verifyTailscale(path string) error { +func verifyAuthenticode(path string) error { return authenticode.Verify(path, certSubjectTailscale) } + +func (up *Updater) updateWindows() error { + if msi := os.Getenv(winMSIEnv); msi != "" { + // stdout/stderr from this part of the install could be lost since the + // parent tailscaled is replaced. Create a temp log file to have some + // output to debug with in case update fails. + close, err := up.switchOutputToFile() + if err != nil { + up.Logf("failed to create log file for installation: %v; proceeding with existing outputs", err) + } else { + defer close.Close() + } + + up.Logf("installing %v ...", msi) + if err := up.installMSI(msi); err != nil { + up.Logf("MSI install failed: %v", err) + return err + } + + up.Logf("success.") + return nil + } + + if !winutil.IsCurrentProcessElevated() { + return errors.New(`update must be run as Administrator + +you can run the command prompt as Administrator one of these ways: +* right-click cmd.exe, select 'Run as administrator' +* press Windows+x, then press a +* press Windows+r, type in "cmd", then press Ctrl+Shift+Enter`) + } + ver, err := requestedTailscaleVersion(up.Version, up.Track) + if err != nil { + return err + } + arch := runtime.GOARCH + if arch == "386" { + arch = "x86" + } + if !up.confirm(ver) { + return nil + } + + tsDir := filepath.Join(os.Getenv("ProgramData"), "Tailscale") + msiDir := filepath.Join(tsDir, "MSICache") + if fi, err := os.Stat(tsDir); err != nil { + return fmt.Errorf("expected %s to exist, got stat error: %w", tsDir, err) + } else if !fi.IsDir() { + return fmt.Errorf("expected %s to be a directory; got %v", tsDir, fi.Mode()) + } + if err := os.MkdirAll(msiDir, 0700); err != nil { + return err + } + up.cleanupOldDownloads(filepath.Join(msiDir, "*.msi")) + pkgsPath := fmt.Sprintf("%s/tailscale-setup-%s-%s.msi", up.Track, ver, arch) + msiTarget := filepath.Join(msiDir, path.Base(pkgsPath)) + if err := up.downloadURLToFile(pkgsPath, msiTarget); err != nil { + return err + } + + up.Logf("verifying MSI authenticode...") + if err := verifyAuthenticode(msiTarget); err != nil { + return fmt.Errorf("authenticode verification of %s failed: %w", msiTarget, err) + } + up.Logf("authenticode verification succeeded") + + up.Logf("making tailscale.exe copy to switch to...") + up.cleanupOldDownloads(filepath.Join(os.TempDir(), "tailscale-updater-*.exe")) + selfOrig, selfCopy, err := makeSelfCopy() + if err != nil { + return err + } + defer os.Remove(selfCopy) + up.Logf("running tailscale.exe copy for final install...") + + cmd := exec.Command(selfCopy, "update") + cmd.Env = append(os.Environ(), winMSIEnv+"="+msiTarget, winExePathEnv+"="+selfOrig) + cmd.Stdout = up.Stderr + cmd.Stderr = up.Stderr + cmd.Stdin = os.Stdin + if err := cmd.Start(); err != nil { + return err + } + // Once it's started, exit ourselves, so the binary is free + // to be replaced. + os.Exit(0) + panic("unreachable") +} + +func (up *Updater) installMSI(msi string) error { + var err error + for tries := 0; tries < 2; tries++ { + cmd := exec.Command("msiexec.exe", "/i", filepath.Base(msi), "/quiet", "/norestart", "/qn") + cmd.Dir = filepath.Dir(msi) + cmd.Stdout = up.Stdout + cmd.Stderr = up.Stderr + cmd.Stdin = os.Stdin + err = cmd.Run() + if err == nil { + break + } + up.Logf("Install attempt failed: %v", err) + uninstallVersion := up.currentVersion + if v := os.Getenv("TS_DEBUG_UNINSTALL_VERSION"); v != "" { + uninstallVersion = v + } + // Assume it's a downgrade, which msiexec won't permit. Uninstall our current version first. + up.Logf("Uninstalling current version %q for downgrade...", uninstallVersion) + cmd = exec.Command("msiexec.exe", "/x", msiUUIDForVersion(uninstallVersion), "/norestart", "/qn") + cmd.Stdout = up.Stdout + cmd.Stderr = up.Stderr + cmd.Stdin = os.Stdin + err = cmd.Run() + up.Logf("msiexec uninstall: %v", err) + } + return err +} + +func msiUUIDForVersion(ver string) string { + arch := runtime.GOARCH + if arch == "386" { + arch = "x86" + } + track, err := versionToTrack(ver) + if err != nil { + track = UnstableTrack + } + msiURL := fmt.Sprintf("https://pkgs.tailscale.com/%s/tailscale-setup-%s-%s.msi", track, ver, arch) + return "{" + strings.ToUpper(uuid.NewSHA1(uuid.NameSpaceURL, []byte(msiURL)).String()) + "}" +} + +func (up *Updater) switchOutputToFile() (io.Closer, error) { + var logFilePath string + exePath, err := os.Executable() + if err != nil { + logFilePath = filepath.Join(os.TempDir(), "tailscale-updater.log") + } else { + logFilePath = strings.TrimSuffix(exePath, ".exe") + ".log" + } + + up.Logf("writing update output to %q", logFilePath) + logFile, err := os.Create(logFilePath) + if err != nil { + return nil, err + } + + up.Logf = func(m string, args ...any) { + fmt.Fprintf(logFile, m+"\n", args...) + } + up.Stdout = logFile + up.Stderr = logFile + return logFile, nil +} diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 7b9d80af8..8c9a9b285 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -26,7 +26,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L github.com/google/nftables/expr from github.com/google/nftables+ L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+ L github.com/google/nftables/xt from github.com/google/nftables/expr+ - github.com/google/uuid from tailscale.com/clientupdate+ + DW github.com/google/uuid from tailscale.com/clientupdate+ github.com/gorilla/csrf from tailscale.com/client/web github.com/gorilla/securecookie from github.com/gorilla/csrf github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+ @@ -178,7 +178,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli tailscale.com/util/usermetric from tailscale.com/health tailscale.com/util/vizerror from tailscale.com/tailcfg+ - 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ + W 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+ W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+ tailscale.com/version from tailscale.com/client/web+ @@ -258,7 +258,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/tls from github.com/miekg/dns+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - database/sql/driver from github.com/google/uuid + DW database/sql/driver from github.com/google/uuid W debug/dwarf from debug/pe W debug/pe from github.com/dblohm7/wingoes/pe embed from crypto/internal/nistec+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 0d8e51eda..3661f9a50 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -111,7 +111,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de L github.com/google/nftables/expr from github.com/google/nftables+ L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+ L github.com/google/nftables/xt from github.com/google/nftables/expr+ - github.com/google/uuid from tailscale.com/clientupdate+ + DW github.com/google/uuid from tailscale.com/clientupdate+ github.com/gorilla/csrf from tailscale.com/client/web github.com/gorilla/securecookie from github.com/gorilla/csrf github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+ @@ -508,7 +508,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - database/sql/driver from github.com/google/uuid + DW database/sql/driver from github.com/google/uuid W debug/dwarf from debug/pe W debug/pe from github.com/dblohm7/wingoes/pe embed from crypto/internal/nistec+ diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 7c076e8ab..528304bab 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -31,7 +31,6 @@ "sync" "time" - "github.com/google/uuid" "golang.org/x/net/dns/dnsmessage" "tailscale.com/client/tailscale/apitype" "tailscale.com/clientupdate" @@ -1563,7 +1562,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) { switch r.Method { case "PUT": file := ipn.OutgoingFile{ - ID: uuid.Must(uuid.NewRandom()).String(), + ID: rands.HexString(30), PeerID: peerID, Name: filenameEscaped, DeclaredSize: r.ContentLength, diff --git a/tstest/iosdeps/iosdeps_test.go b/tstest/iosdeps/iosdeps_test.go index 40e084c21..9679e2cfc 100644 --- a/tstest/iosdeps/iosdeps_test.go +++ b/tstest/iosdeps/iosdeps_test.go @@ -20,6 +20,8 @@ func TestDeps(t *testing.T) { "tailscale.com/net/wsconn": "https://github.com/tailscale/tailscale/issues/13762", "github.com/coder/websocket": "https://github.com/tailscale/tailscale/issues/13762", "github.com/mitchellh/go-ps": "https://github.com/tailscale/tailscale/pull/13759", + "database/sql/driver": "iOS doesn't use an SQL database", + "github.com/google/uuid": "see tailscale/tailscale#13760", }, }.Check(t) }