From 3ed776169cda830f317607de8e216c2cbf46fa66 Mon Sep 17 00:00:00 2001 From: hugoShaka Date: Thu, 24 Jul 2025 11:51:05 -0400 Subject: [PATCH 1/2] Gracefully fail when cannot do client tool update --- build.assets/charts/Dockerfile-distroless | 1 + lib/autoupdate/tools/helper.go | 22 ++++++++++++++++++---- lib/autoupdate/tools/updater.go | 9 ++++++--- lib/autoupdate/tools/utils.go | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/build.assets/charts/Dockerfile-distroless b/build.assets/charts/Dockerfile-distroless index afc9ef481d768..3df8ee5f4f56d 100644 --- a/build.assets/charts/Dockerfile-distroless +++ b/build.assets/charts/Dockerfile-distroless @@ -33,6 +33,7 @@ FROM $BASE_IMAGE COPY --from=teleport /opt/staging / COPY --from=staging /opt/staging/root / COPY --from=staging /opt/staging/status /var/lib/dpkg/status.d +ENV TELEPORT_TOOLS_VERSION=off # Attempt a graceful shutdown by default # See https://goteleport.com/docs/reference/signals/ for signal reference. STOPSIGNAL SIGQUIT diff --git a/lib/autoupdate/tools/helper.go b/lib/autoupdate/tools/helper.go index f2e45b286d052..01bc2fe9534cb 100644 --- a/lib/autoupdate/tools/helper.go +++ b/lib/autoupdate/tools/helper.go @@ -69,6 +69,12 @@ func newUpdater(toolsDir string) (*Updater, error) { // If they differ, the requested version is downloaded and extracted into the client tools directory, // the installation is recorded in the configuration file, and the tool is re-executed with the updated version. func CheckAndUpdateLocal(ctx context.Context, currentProfileName string, reExecArgs []string) error { + // If client tools updates are explicitly disabled, we want to catch this as soon as possible + // so we don't try to read te user home directory, fail, and log warnings. + if os.Getenv(teleportToolsVersionEnv) == teleportToolsVersionEnvDisabled { + return nil + } + var err error if currentProfileName == "" { home := os.Getenv(types.HomeEnvVar) @@ -84,12 +90,13 @@ func CheckAndUpdateLocal(ctx context.Context, currentProfileName string, reExecA toolsDir, err := Dir() if err != nil { - slog.WarnContext(ctx, "Client tools update is disabled", "error", err) + slog.WarnContext(ctx, "Failed to detect the teleport home directory, client tools updates are disabled", "error", err) return nil } updater, err := newUpdater(toolsDir) if err != nil { - return trace.Wrap(err) + slog.WarnContext(ctx, "Failed to create the updater, client tools updates are disabled", "error", err) + return nil } slog.DebugContext(ctx, "Attempting to local update", "current_profile_name", currentProfileName) @@ -117,14 +124,21 @@ func CheckAndUpdateLocal(ctx context.Context, currentProfileName string, reExecA // the installed version is recorded in the configuration, and the tool is re-executed // with the updated version. func CheckAndUpdateRemote(ctx context.Context, currentProfileName string, insecure bool, reExecArgs []string) error { + // If client tools updates are explicitly disabled, we want to catch this as soon as possible + // so we don't try to read te user home directory, fail, and log warnings. + if os.Getenv(teleportToolsVersionEnv) == teleportToolsVersionEnvDisabled { + return nil + } + toolsDir, err := Dir() if err != nil { - slog.WarnContext(ctx, "Client tools update is disabled", "error", err) + slog.WarnContext(ctx, "Failed to detect the teleport home directory, client tools updates are disabled", "error", err) return nil } updater, err := newUpdater(toolsDir) if err != nil { - return trace.Wrap(err) + slog.WarnContext(ctx, "Failed to create the updater, client tools updates are disabled", "error", err) + return nil } slog.DebugContext(ctx, "Attempting to remote update", "current_profile_name", currentProfileName, "insecure", insecure) diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go index 4f439ba03bcb4..1d470e586d131 100644 --- a/lib/autoupdate/tools/updater.go +++ b/lib/autoupdate/tools/updater.go @@ -52,6 +52,9 @@ import ( const ( // teleportToolsVersionEnv is environment name for requesting specific version for update. teleportToolsVersionEnv = "TELEPORT_TOOLS_VERSION" + // teleportToolsVersionEnvDisabled is a special value that disables teleport tools updates + // when assigned to the teleportToolsVersionEnv environment variable. + teleportToolsVersionEnvDisabled = "off" // teleportToolsVersionReExecEnv is internal environment name for transferring original // version to re-executed ones. teleportToolsVersionReExecEnv = "TELEPORT_TOOLS_VERSION_REEXEC" @@ -150,7 +153,7 @@ func (u *Updater) CheckLocal(ctx context.Context, profileName string) (resp *Upd requestedVersion := os.Getenv(teleportToolsVersionEnv) switch requestedVersion { // The user has turned off any form of automatic updates. - case "off": + case teleportToolsVersionEnvDisabled: return &UpdateResponse{Version: "", ReExec: false}, nil // Requested version already the same as client version. case u.localVersion: @@ -213,7 +216,7 @@ func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bo requestedVersion := os.Getenv(teleportToolsVersionEnv) switch requestedVersion { // The user has turned off any form of automatic updates. - case "off": + case teleportToolsVersionEnvDisabled: return &UpdateResponse{Version: "", ReExec: false}, nil // Requested version already the same as client version. case u.localVersion: @@ -427,7 +430,7 @@ func (u *Updater) Exec(ctx context.Context, toolsVersion string, args []string) if err := os.Unsetenv(teleportToolsVersionEnv); err != nil { return 0, trace.Wrap(err) } - env = append(env, teleportToolsVersionEnv+"=off") + env = append(env, teleportToolsVersionEnv+"="+teleportToolsVersionEnvDisabled) slog.DebugContext(ctx, "Disable next re-execution") } env = append(env, fmt.Sprintf("%s=%s", teleportToolsVersionReExecEnv, u.localVersion)) diff --git a/lib/autoupdate/tools/utils.go b/lib/autoupdate/tools/utils.go index dcd9b1f06ebda..0a32aee531ddd 100644 --- a/lib/autoupdate/tools/utils.go +++ b/lib/autoupdate/tools/utils.go @@ -113,7 +113,7 @@ func CheckToolVersion(toolPath string) (string, error) { // Execute "{tsh, tctl} version" and pass in TELEPORT_TOOLS_VERSION=off to // turn off all automatic updates code paths to prevent any recursion. command := exec.CommandContext(ctx, toolPath, "version") - command.Env = []string{teleportToolsVersionEnv + "=off"} + command.Env = []string{teleportToolsVersionEnv + "=" + teleportToolsVersionEnvDisabled} output, err := command.Output() if err != nil { slog.DebugContext(context.Background(), "failed to determine version", From 1487f7d02e148208d2de96f6f19708d2118e44ce Mon Sep 17 00:00:00 2001 From: hugoShaka Date: Thu, 24 Jul 2025 12:20:57 -0400 Subject: [PATCH 2/2] Gracefully fail when cannot check the version --- lib/autoupdate/tools/helper.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/autoupdate/tools/helper.go b/lib/autoupdate/tools/helper.go index 01bc2fe9534cb..bad8752e5123f 100644 --- a/lib/autoupdate/tools/helper.go +++ b/lib/autoupdate/tools/helper.go @@ -102,7 +102,8 @@ func CheckAndUpdateLocal(ctx context.Context, currentProfileName string, reExecA slog.DebugContext(ctx, "Attempting to local update", "current_profile_name", currentProfileName) resp, err := updater.CheckLocal(ctx, currentProfileName) if err != nil { - return trace.Wrap(err) + slog.WarnContext(ctx, "Failed to check local teleport versions, client tools updates are disabled", "error", err) + return nil } if resp.ReExec { @@ -144,7 +145,8 @@ func CheckAndUpdateRemote(ctx context.Context, currentProfileName string, insecu slog.DebugContext(ctx, "Attempting to remote update", "current_profile_name", currentProfileName, "insecure", insecure) resp, err := updater.CheckRemote(ctx, currentProfileName, insecure) if err != nil { - return trace.Wrap(err) + slog.WarnContext(ctx, "Failed to check remote teleport versions, client tools updates are disabled", "error", err) + return nil } if !resp.Disabled && resp.ReExec {