From ce71412004ee50416964a11e6452ee5bdb02c6b1 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Tue, 19 Nov 2024 11:34:12 -0500 Subject: [PATCH] Disable client tools auto update disabled if there are no home dir (#49199) Move updater to general tools package --- integration/autoupdate/tools/updater/main.go | 1 - integration/autoupdate/tools/updater_test.go | 3 - lib/autoupdate/tools/updater.go | 20 +++- lib/client/api.go | 34 +----- tool/common/updater/client_tools.go | 112 +++++++++++++++++++ tool/tctl/common/tctl.go | 38 +------ tool/tsh/common/tsh.go | 78 ++----------- 7 files changed, 138 insertions(+), 148 deletions(-) create mode 100644 tool/common/updater/client_tools.go diff --git a/integration/autoupdate/tools/updater/main.go b/integration/autoupdate/tools/updater/main.go index 775c7ab7b2e9d..8a12dbbd1c9f7 100644 --- a/integration/autoupdate/tools/updater/main.go +++ b/integration/autoupdate/tools/updater/main.go @@ -43,7 +43,6 @@ func main() { ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) updater := tools.NewUpdater( - tools.DefaultClientTools(), toolsDir, version, tools.WithBaseURL(baseURL), diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go index d429a7483910a..ffb3a3300716c 100644 --- a/integration/autoupdate/tools/updater_test.go +++ b/integration/autoupdate/tools/updater_test.go @@ -49,7 +49,6 @@ func TestUpdate(t *testing.T) { // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. updater := tools.NewUpdater( - tools.DefaultClientTools(), toolsDir, testVersions[0], tools.WithBaseURL(baseURL), @@ -91,7 +90,6 @@ func TestParallelUpdate(t *testing.T) { // Initial fetch the updater binary un-archive and replace. updater := tools.NewUpdater( - tools.DefaultClientTools(), toolsDir, testVersions[0], tools.WithBaseURL(baseURL), @@ -165,7 +163,6 @@ func TestUpdateInterruptSignal(t *testing.T) { // Initial fetch the updater binary un-archive and replace. updater := tools.NewUpdater( - tools.DefaultClientTools(), toolsDir, testVersions[0], tools.WithBaseURL(baseURL), diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go index 96352e34d9910..8bad07c395391 100644 --- a/lib/autoupdate/tools/updater.go +++ b/lib/autoupdate/tools/updater.go @@ -82,6 +82,13 @@ func WithClient(client *http.Client) Option { } } +// WithTools defines custom list of the tools has to be installed by updater. +func WithTools(tools []string) Option { + return func(u *Updater) { + u.tools = tools + } +} + // Updater is updater implementation for the client tools auto updates. type Updater struct { toolsDir string @@ -92,13 +99,14 @@ type Updater struct { client *http.Client } -// NewUpdater initializes the updater for client tools auto updates. We need to specify the list -// of tools (e.g., `tsh`, `tctl`) that should be updated, the tools directory path where we -// download, extract package archives with the new version, and replace symlinks (e.g., `$TELEPORT_HOME/bin`). -// The base URL of the CDN with Teleport packages and the `http.Client` can be customized via options. -func NewUpdater(tools []string, toolsDir string, localVersion string, options ...Option) *Updater { +// NewUpdater initializes the updater for client tools auto updates. We need to specify the tools directory +// path where we download, extract package archives with the new version, and replace symlinks +// (e.g., `$TELEPORT_HOME/bin`). +// The base URL of the CDN with Teleport packages, the `http.Client` and the list of tools (e.g., `tsh`, `tctl`) +// that must be updated can be customized via options. +func NewUpdater(toolsDir, localVersion string, options ...Option) *Updater { updater := &Updater{ - tools: tools, + tools: DefaultClientTools(), toolsDir: toolsDir, localVersion: localVersion, baseURL: baseURL, diff --git a/lib/client/api.go b/lib/client/api.go index ef72a5fb574ee..012875f09b73c 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -74,7 +74,6 @@ import ( "github.com/gravitational/teleport/lib/auth/touchid" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" "github.com/gravitational/teleport/lib/authz" - "github.com/gravitational/teleport/lib/autoupdate/tools" libmfa "github.com/gravitational/teleport/lib/client/mfa" "github.com/gravitational/teleport/lib/client/sso" "github.com/gravitational/teleport/lib/client/terminal" @@ -96,7 +95,7 @@ import ( "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/agentconn" logutils "github.com/gravitational/teleport/lib/utils/log" - "github.com/gravitational/teleport/lib/utils/signal" + "github.com/gravitational/teleport/tool/common/updater" ) const ( @@ -710,38 +709,9 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error, return trace.Wrap(err) } - // The user has typed a command like `tsh ssh ...` without being logged in, - // if the running binary needs to be updated, update and re-exec. - // - // If needed, download the new version of {tsh, tctl} and re-exec. Make - // sure to exit this process with the same exit code as the child process. - // - toolsDir, err := tools.Dir() - if err != nil { + if err := updater.CheckAndUpdateRemote(ctx, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { return trace.Wrap(err) } - updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version) - toolsVersion, reExec, err := updater.CheckRemote(ctx, tc.WebProxyAddr, tc.InsecureSkipVerify) - if err != nil { - return trace.Wrap(err) - } - if reExec { - ctxUpdate, cancel := signal.GetSignalHandler().NotifyContext(context.Background()) - defer cancel() - // Download the version of client tools required by the cluster. - err := updater.UpdateWithLock(ctxUpdate, toolsVersion) - if err != nil && !errors.Is(err, context.Canceled) { - utils.FatalError(err) - } - // Re-execute client tools with the correct version of client tools. - code, err := updater.Exec() - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Debugf("Failed to re-exec client tool: %v.", err) - os.Exit(code) - } else if err == nil { - os.Exit(code) - } - } if opt.afterLoginHook != nil { if err := opt.afterLoginHook(); err != nil { diff --git a/tool/common/updater/client_tools.go b/tool/common/updater/client_tools.go new file mode 100644 index 0000000000000..6949664b35b7d --- /dev/null +++ b/tool/common/updater/client_tools.go @@ -0,0 +1,112 @@ +/* + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package updater + +import ( + "context" + "errors" + "log/slog" + "os" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/lib/autoupdate/tools" + stacksignal "github.com/gravitational/teleport/lib/utils/signal" +) + +// CheckAndUpdateLocal verifies if the TELEPORT_TOOLS_VERSION environment variable +// is set and a version is defined (or disabled by setting it to "off"). The requested +// version is compared with the current client tools version. If they differ, the version +// package is downloaded, extracted to the client tools directory, and re-executed +// with the updated version. +// If $TELEPORT_HOME/bin contains downloaded client tools, it always re-executes +// using the version from the home directory. +func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error { + toolsDir, err := tools.Dir() + if err != nil { + slog.WarnContext(ctx, "Client tools update is disabled", "error", err) + return nil + } + updater := tools.NewUpdater(toolsDir, currentVersion) + // At process startup, check if a version has already been downloaded to + // $TELEPORT_HOME/bin or if the user has set the TELEPORT_TOOLS_VERSION + // environment variable. If so, re-exec that version of client tools. + toolsVersion, reExec, err := updater.CheckLocal() + if err != nil { + return trace.Wrap(err) + } + if reExec { + return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion)) + } + + return nil +} + +// CheckAndUpdateRemote verifies client tools version is set for update in cluster +// configuration by making the http request to `webapi/find` endpoint. The requested +// version is compared with the current client tools version. If they differ, the version +// package is downloaded, extracted to the client tools directory, and re-executed +// with the updated version. +// If $TELEPORT_HOME/bin contains downloaded client tools, it always re-executes +// using the version from the home directory. +func CheckAndUpdateRemote(ctx context.Context, currentVersion string, proxy string, insecure bool) error { + toolsDir, err := tools.Dir() + if err != nil { + slog.WarnContext(ctx, "Client tools update is disabled", "error", err) + return nil + } + updater := tools.NewUpdater(toolsDir, currentVersion) + // The user has typed a command like `tsh ssh ...` without being logged in, + // if the running binary needs to be updated, update and re-exec. + // + // If needed, download the new version of client tools and re-exec. Make + // sure to exit this process with the same exit code as the child process. + toolsVersion, reExec, err := updater.CheckRemote(ctx, proxy, insecure) + if err != nil { + return trace.Wrap(err) + } + if reExec { + return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion)) + } + + return nil +} + +func updateAndReExec(ctx context.Context, updater *tools.Updater, toolsVersion string) error { + ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(ctx) + defer cancel() + // Download the version of client tools required by the cluster. This + // is required if the user passed in the TELEPORT_TOOLS_VERSION + // explicitly. + err := updater.UpdateWithLock(ctxUpdate, toolsVersion) + if err != nil && !errors.Is(err, context.Canceled) { + return trace.Wrap(err) + } + + // Re-execute client tools with the correct version of client tools. + code, err := updater.Exec() + if err != nil && !errors.Is(err, os.ErrNotExist) { + slog.DebugContext(ctx, "Failed to re-exec client tool", "error", err) + os.Exit(code) + } else if err == nil { + os.Exit(code) + } + + return nil +} diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index 5af22702f8b17..fa987c6031ea3 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -44,7 +44,6 @@ import ( "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/state" "github.com/gravitational/teleport/lib/auth/storage" - "github.com/gravitational/teleport/lib/autoupdate/tools" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/client/identityfile" libmfa "github.com/gravitational/teleport/lib/client/mfa" @@ -56,8 +55,8 @@ import ( "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/hostid" - "github.com/gravitational/teleport/lib/utils/signal" "github.com/gravitational/teleport/tool/common" + "github.com/gravitational/teleport/tool/common/updater" ) const ( @@ -108,42 +107,11 @@ type CLICommand interface { // // distribution: name of the Teleport distribution func Run(ctx context.Context, commands []CLICommand) { - // The user has typed a command like `tsh ssh ...` without being logged in, - // if the running binary needs to be updated, update and re-exec. - // - // If needed, download the new version of {tsh, tctl} and re-exec. Make - // sure to exit this process with the same exit code as the child process. - // - toolsDir, err := tools.Dir() - if err != nil { - utils.FatalError(err) - } - updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version) - toolsVersion, reExec, err := updater.CheckLocal() - if err != nil { + if err := updater.CheckAndUpdateLocal(ctx, teleport.Version); err != nil { utils.FatalError(err) } - if reExec { - ctxUpdate, cancel := signal.GetSignalHandler().NotifyContext(ctx) - defer cancel() - // Download the version of client tools required by the cluster. This - // is required if the user passed in the TELEPORT_TOOLS_VERSION - // explicitly. - err := updater.UpdateWithLock(ctxUpdate, toolsVersion) - if err != nil && !errors.Is(err, context.Canceled) { - utils.FatalError(err) - } - // Re-execute client tools with the correct version of client tools. - code, err := updater.Exec() - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Debugf("Failed to re-exec client tool: %v.", err) - os.Exit(code) - } else if err == nil { - os.Exit(code) - } - } - err = TryRun(commands, os.Args[1:]) + err := TryRun(commands, os.Args[1:]) if err != nil { var exitError *common.ExitCodeError if errors.As(err, &exitError) { diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 0ef322caa460a..a3523876116e4 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -73,7 +73,6 @@ import ( "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth/authclient" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" - "github.com/gravitational/teleport/lib/autoupdate/tools" "github.com/gravitational/teleport/lib/benchmark" benchmarkdb "github.com/gravitational/teleport/lib/benchmark/db" "github.com/gravitational/teleport/lib/client" @@ -98,6 +97,7 @@ import ( "github.com/gravitational/teleport/tool/common" "github.com/gravitational/teleport/tool/common/fido2" "github.com/gravitational/teleport/tool/common/touchid" + "github.com/gravitational/teleport/tool/common/updater" "github.com/gravitational/teleport/tool/common/webauthnwin" ) @@ -708,37 +708,9 @@ func initLogger(cf *CLIConf) { // // DO NOT RUN TESTS that call Run() in parallel (unless you taken precautions). func Run(ctx context.Context, args []string, opts ...CliOption) error { - // At process startup, check if a version has already been downloaded to - // $TELEPORT_HOME/bin or if the user has set the TELEPORT_TOOLS_VERSION - // environment variable. If so, re-exec that version of {tsh, tctl}. - toolsDir, err := tools.Dir() - if err != nil { - return trace.Wrap(err) - } - updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version) - toolsVersion, reExec, err := updater.CheckLocal() - if err != nil { + if err := updater.CheckAndUpdateLocal(ctx, teleport.Version); err != nil { return trace.Wrap(err) } - if reExec { - ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(ctx) - defer cancel() - // Download the version of client tools required by the cluster. This - // is required if the user passed in the TELEPORT_TOOLS_VERSION - // explicitly. - err := updater.UpdateWithLock(ctxUpdate, toolsVersion) - if err != nil && !errors.Is(err, context.Canceled) { - return trace.Wrap(err) - } - // Re-execute client tools with the correct version of client tools. - code, err := updater.Exec() - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Debugf("Failed to re-exec client tool: %v.", err) - os.Exit(code) - } else if err == nil { - os.Exit(code) - } - } cf := CLIConf{ Context: ctx, @@ -1273,6 +1245,7 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { bench.Hidden() } + var err error cf.executablePath, err = os.Executable() if err != nil { return trace.Wrap(err) @@ -1901,7 +1874,7 @@ func onLogin(cf *CLIConf) error { // The user is not logged in and has typed in `tsh --proxy=... login`, if // the running binary needs to be updated, update and re-exec. if profile == nil { - if err := updateAndRun(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { + if err := updater.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { return trace.Wrap(err) } } @@ -1919,7 +1892,7 @@ func onLogin(cf *CLIConf) error { // The user has typed `tsh login`, if the running binary needs to // be updated, update and re-exec. - if err := updateAndRun(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { + if err := updater.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { return trace.Wrap(err) } @@ -1939,7 +1912,7 @@ func onLogin(cf *CLIConf) error { // The user has typed `tsh login`, if the running binary needs to // be updated, update and re-exec. - if err := updateAndRun(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { + if err := updater.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { return trace.Wrap(err) } @@ -2015,7 +1988,7 @@ func onLogin(cf *CLIConf) error { default: // The user is logged in and has typed in `tsh --proxy=... login`, if // the running binary needs to be updated, update and re-exec. - if err := updateAndRun(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { + if err := updater.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil { return trace.Wrap(err) } } @@ -5625,43 +5598,6 @@ const ( "https://goteleport.com/docs/access-controls/guides/headless/#troubleshooting" ) -func updateAndRun(ctx context.Context, proxy string, insecure bool) error { - // The user has typed a command like `tsh ssh ...` without being logged in, - // if the running binary needs to be updated, update and re-exec. - // - // If needed, download the new version of {tsh, tctl} and re-exec. Make - // sure to exit this process with the same exit code as the child process. - // - toolsDir, err := tools.Dir() - if err != nil { - return trace.Wrap(err) - } - updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version) - toolsVersion, reExec, err := updater.CheckRemote(ctx, proxy, insecure) - if err != nil { - return trace.Wrap(err) - } - if reExec { - ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(context.Background()) - defer cancel() - // Download the version of client tools required by the cluster. - err := updater.UpdateWithLock(ctxUpdate, toolsVersion) - if err != nil && !errors.Is(err, context.Canceled) { - return trace.Wrap(err) - } - // Re-execute client tools with the correct version of client tools. - code, err := updater.Exec() - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Debugf("Failed to re-exec client tool: %v.", err) - os.Exit(code) - } else if err == nil { - os.Exit(code) - } - } - - return nil -} - // Lock the process memory to prevent rsa keys and certificates in memory from being exposed in a swap. func tryLockMemory(cf *CLIConf) error { if cf.MlockMode == mlockModeAuto {