diff --git a/integration/autoupdate/tools/main_test.go b/integration/autoupdate/tools/main_test.go
index bbc3f559f65c0..10517f7d1e0f0 100644
--- a/integration/autoupdate/tools/main_test.go
+++ b/integration/autoupdate/tools/main_test.go
@@ -39,6 +39,7 @@ import (
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/integration/helpers/archive"
+ "github.com/gravitational/teleport/lib/modules"
)
const (
@@ -59,6 +60,7 @@ var (
)
func TestMain(m *testing.M) {
+ modules.SetInsecureTestMode(true)
ctx := context.Background()
tmp, err := os.MkdirTemp(os.TempDir(), testBinaryName)
if err != nil {
@@ -139,7 +141,7 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
case constants.DarwinOS:
output = filepath.Join(versionPath, app+".app", "Contents", "MacOS", app)
}
- if err := buildBinary(output, toolsDir, version, baseURL); err != nil {
+ if err := buildBinary(output, toolsDir, version, baseURL, app); err != nil {
return trace.Wrap(err)
}
}
@@ -156,16 +158,17 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
}
}
-// buildBinary executes command to build binary with updater logic only for testing.
-func buildBinary(output string, toolsDir string, version string, baseURL string) error {
+// buildBinary executes command to build client tool binary with updater logic for testing.
+func buildBinary(output string, toolsDir string, version string, baseURL string, app string) error {
cmd := exec.Command(
"go", "build", "-o", output,
"-ldflags", strings.Join([]string{
- fmt.Sprintf("-X 'main.toolsDir=%s'", toolsDir),
- fmt.Sprintf("-X 'main.version=%s'", version),
- fmt.Sprintf("-X 'main.baseURL=%s'", baseURL),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/integration/autoupdate/tools/updater.version=%s'", version),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/lib/autoupdate/tools.toolsDir=%s'", toolsDir),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/lib/autoupdate/tools.version=%s'", version),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/lib/autoupdate/tools.baseURL=%s'", baseURL),
}, " "),
- "./updater",
+ fmt.Sprintf("./updater/%s", app),
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
diff --git a/integration/autoupdate/tools/updater/main.go b/integration/autoupdate/tools/updater/main.go
deleted file mode 100644
index 8a12dbbd1c9f7..0000000000000
--- a/integration/autoupdate/tools/updater/main.go
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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 main
-
-import (
- "context"
- "errors"
- "fmt"
- "log"
- "os"
- "os/signal"
- "syscall"
- "time"
-
- "github.com/gravitational/teleport/lib/autoupdate/tools"
-)
-
-var (
- version = "development"
- baseURL = "http://localhost"
- toolsDir = ""
-)
-
-func main() {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
- defer cancel()
- ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
-
- updater := tools.NewUpdater(
- toolsDir,
- version,
- tools.WithBaseURL(baseURL),
- )
- toolsVersion, reExec, err := updater.CheckLocal()
- if err != nil {
- log.Fatal(err)
- }
- if reExec {
- // Download and update 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(ctx, toolsVersion)
- if errors.Is(err, context.Canceled) {
- os.Exit(0)
- return
- }
- if err != nil {
- log.Fatalf("failed to download version (%v): %v\n", toolsVersion, err)
- return
- }
-
- // Re-execute client tools with the correct version of client tools.
- code, err := updater.Exec()
- if err != nil {
- log.Fatalf("Failed to re-exec client tool: %v\n", err)
- } else {
- os.Exit(code)
- }
- }
- if len(os.Args) > 1 && os.Args[1] == "version" {
- fmt.Printf("Teleport v%v git\n", version)
- }
-}
diff --git a/integration/autoupdate/tools/updater/modules.go b/integration/autoupdate/tools/updater/modules.go
new file mode 100644
index 0000000000000..a146fc6031eda
--- /dev/null
+++ b/integration/autoupdate/tools/updater/modules.go
@@ -0,0 +1,107 @@
+/*
+ * 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"
+ "crypto"
+ "fmt"
+ "time"
+
+ "github.com/gravitational/trace"
+
+ "github.com/gravitational/teleport"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/types/accesslist"
+ "github.com/gravitational/teleport/api/utils/keys"
+ "github.com/gravitational/teleport/entitlements"
+ "github.com/gravitational/teleport/lib/modules"
+ "github.com/gravitational/teleport/lib/tlsca"
+)
+
+const (
+ TestPassword = "abc123def456"
+)
+
+var (
+ version = teleport.Version
+)
+
+type TestModules struct{}
+
+func (p *TestModules) GenerateAccessRequestPromotions(context.Context, modules.AccessResourcesGetter, types.AccessRequest) (*types.AccessRequestAllowedPromotions, error) {
+ return &types.AccessRequestAllowedPromotions{}, nil
+}
+
+func (p *TestModules) GetSuggestedAccessLists(context.Context, *tlsca.Identity, modules.AccessListSuggestionClient, modules.AccessListAndMembersGetter, string) ([]*accesslist.AccessList, error) {
+ return []*accesslist.AccessList{}, nil
+}
+
+// BuildType returns build type (OSS or Enterprise)
+func (p *TestModules) BuildType() string {
+ return "CLI"
+}
+
+// IsEnterpriseBuild returns false for [TestModules].
+func (p *TestModules) IsEnterpriseBuild() bool {
+ return false
+}
+
+// IsOSSBuild returns false for [TestModules].
+func (p *TestModules) IsOSSBuild() bool {
+ return false
+}
+
+// LicenseExpiry returns the expiry date of the enterprise license, if applicable.
+func (p *TestModules) LicenseExpiry() time.Time {
+ return time.Time{}
+}
+
+// PrintVersion prints the Teleport version.
+func (p *TestModules) PrintVersion() {
+ fmt.Printf("Teleport v%v git\n", version)
+}
+
+// Features returns supported features
+func (p *TestModules) Features() modules.Features {
+ return modules.Features{
+ Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{},
+ AdvancedAccessWorkflows: true,
+ }
+}
+
+// IsBoringBinary checks if the binary was compiled with BoringCrypto.
+func (p *TestModules) IsBoringBinary() bool {
+ return false
+}
+
+// AttestHardwareKey attests a hardware key.
+func (p *TestModules) AttestHardwareKey(context.Context, interface{}, *keys.AttestationStatement, crypto.PublicKey, time.Duration) (*keys.AttestationData, error) {
+ return nil, trace.NotFound("no attestation data for the given key")
+}
+
+func (p *TestModules) EnableRecoveryCodes() {}
+
+func (p *TestModules) EnablePlugins() {}
+
+func (p *TestModules) SetFeatures(f modules.Features) {}
+
+func (p *TestModules) EnableAccessGraph() {}
+
+func (p *TestModules) EnableAccessMonitoring() {}
diff --git a/integration/autoupdate/tools/updater/tctl/main.go b/integration/autoupdate/tools/updater/tctl/main.go
new file mode 100644
index 0000000000000..bbf894618e3f6
--- /dev/null
+++ b/integration/autoupdate/tools/updater/tctl/main.go
@@ -0,0 +1,38 @@
+/*
+ * 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 main
+
+import (
+ "context"
+
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/modules"
+ stacksignal "github.com/gravitational/teleport/lib/utils/signal"
+ tctl "github.com/gravitational/teleport/tool/tctl/common"
+)
+
+func main() {
+ ctx, cancel := stacksignal.GetSignalHandler().NotifyContext(context.Background())
+ defer cancel()
+
+ modules.SetInsecureTestMode(true)
+ modules.SetModules(&updater.TestModules{})
+
+ tctl.Run(ctx, tctl.Commands())
+}
diff --git a/integration/autoupdate/tools/updater/tsh/main.go b/integration/autoupdate/tools/updater/tsh/main.go
new file mode 100644
index 0000000000000..2b878df1b2d3f
--- /dev/null
+++ b/integration/autoupdate/tools/updater/tsh/main.go
@@ -0,0 +1,45 @@
+/*
+ * 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 main
+
+import (
+ "context"
+ "os"
+
+ "github.com/gravitational/teleport/api/utils/prompt"
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/modules"
+ "github.com/gravitational/teleport/lib/utils"
+ stacksignal "github.com/gravitational/teleport/lib/utils/signal"
+ tsh "github.com/gravitational/teleport/tool/tsh/common"
+)
+
+func main() {
+ ctx, cancel := stacksignal.GetSignalHandler().NotifyContext(context.Background())
+ defer cancel()
+
+ modules.SetInsecureTestMode(true)
+ modules.SetModules(&updater.TestModules{})
+ prompt.SetStdin(prompt.NewFakeReader().AddString(updater.TestPassword))
+
+ err := tsh.Run(ctx, os.Args[1:])
+ if err != nil {
+ utils.FatalError(err)
+ }
+}
diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go
index ffb3a3300716c..a64ec9a1f9d0f 100644
--- a/integration/autoupdate/tools/updater_test.go
+++ b/integration/autoupdate/tools/updater_test.go
@@ -33,6 +33,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/autoupdate/tools"
)
@@ -44,6 +45,7 @@ var (
// TestUpdate verifies the basic update logic. We first download a lower version, then request
// an update to a newer version, expecting it to re-execute with the updated version.
func TestUpdate(t *testing.T) {
+ t.Setenv(types.HomeEnvVar, t.TempDir())
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
@@ -57,7 +59,7 @@ func TestUpdate(t *testing.T) {
require.NoError(t, err)
// Verify that the installed version is equal to requested one.
- cmd := exec.CommandContext(ctx, filepath.Join(toolsDir, "tsh"), "version")
+ cmd := exec.CommandContext(ctx, filepath.Join(toolsDir, "tctl"), "version")
out, err := cmd.Output()
require.NoError(t, err)
@@ -85,6 +87,7 @@ func TestUpdate(t *testing.T) {
// first update is complete, other processes should acquire the lock one by one and re-execute
// the command with the updated version without any new downloads.
func TestParallelUpdate(t *testing.T) {
+ t.Setenv(types.HomeEnvVar, t.TempDir())
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
@@ -158,6 +161,7 @@ func TestParallelUpdate(t *testing.T) {
// TestUpdateInterruptSignal verifies the interrupt signal send to the process must stop downloading.
func TestUpdateInterruptSignal(t *testing.T) {
+ t.Setenv(types.HomeEnvVar, t.TempDir())
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
diff --git a/integration/autoupdate/tools/updater_tsh_test.go b/integration/autoupdate/tools/updater_tsh_test.go
new file mode 100644
index 0000000000000..d945fcdeffc56
--- /dev/null
+++ b/integration/autoupdate/tools/updater_tsh_test.go
@@ -0,0 +1,136 @@
+/*
+ * 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 tools_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gopkg.in/yaml.v3"
+
+ "github.com/gravitational/teleport/api/constants"
+ autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/types/autoupdate"
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/autoupdate/tools"
+ "github.com/gravitational/teleport/lib/client"
+ testserver "github.com/gravitational/teleport/tool/teleport/testenv"
+)
+
+// TestAliasLoginWithUpdater runs test cluster with enabled auto updates for client tools,
+// checks that defined alias in tsh configuration is replaced to the proper login command
+// and after auto update this not leads to recursive alias re-execution.
+func TestAliasLoginWithUpdater(t *testing.T) {
+ ctx := context.Background()
+ t.Setenv(types.HomeEnvVar, toolsDir)
+
+ alice, err := types.NewUser("alice")
+ require.NoError(t, err)
+ alice.SetRoles([]string{"access"})
+
+ // Enable client tools auto updates and set the target version.
+ config, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{
+ Tools: &autoupdatev1pb.AutoUpdateConfigSpecTools{
+ Mode: autoupdate.ToolsUpdateModeEnabled,
+ },
+ })
+ require.NoError(t, err)
+ version, err := autoupdate.NewAutoUpdateVersion(&autoupdatev1pb.AutoUpdateVersionSpec{
+ Tools: &autoupdatev1pb.AutoUpdateVersionSpecTools{
+ TargetVersion: testVersions[1], // [v3.2.1]
+ },
+ })
+ require.NoError(t, err)
+
+ // Disable 2fa to simplify login for test.
+ ap, err := types.NewAuthPreferenceFromConfigFile(types.AuthPreferenceSpecV2{
+ Type: constants.Local,
+ SecondFactor: constants.SecondFactorOff,
+ Webauthn: &types.Webauthn{
+ RPID: "localhost",
+ },
+ })
+ require.NoError(t, err)
+
+ rootServer := testserver.MakeTestServer(t,
+ testserver.WithBootstrap(alice),
+ testserver.WithClusterName(t, "root"),
+ testserver.WithAuthPreference(ap),
+ )
+ authService := rootServer.GetAuthServer()
+ _, err = authService.UpsertAutoUpdateConfig(ctx, config)
+ require.NoError(t, err)
+ _, err = authService.UpsertAutoUpdateVersion(ctx, version)
+ require.NoError(t, err)
+ err = authService.UpsertPassword("alice", []byte(updater.TestPassword))
+ require.NoError(t, err)
+
+ // Assign alias to the login command for test cluster.
+ proxyAddr, err := rootServer.ProxyWebAddr()
+ require.NoError(t, err)
+ configPath := filepath.Join(toolsDir, client.TSHConfigPath)
+ require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0700))
+ executable := filepath.Join(toolsDir, "tsh")
+ out, err := yaml.Marshal(client.TSHConfig{
+ Aliases: map[string]string{
+ "loginalice": fmt.Sprintf(
+ "%s login --insecure --proxy %s --user alice --auth %s",
+ executable, proxyAddr, constants.LocalConnector,
+ ),
+ },
+ })
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(configPath, out, 0600))
+
+ // Fetch compiled test binary and install to tools dir [v1.2.3].
+ err = tools.NewUpdater(toolsDir, testVersions[0], tools.WithBaseURL(baseURL)).Update(ctx, testVersions[0])
+ require.NoError(t, err)
+
+ // Execute alias command which must be transformed to the login command.
+ // Since client tools autoupdates is enabled and target version is set
+ // in the test cluster, we have to update client tools to new version.
+ cmd := exec.CommandContext(ctx, executable, "loginalice")
+ cmd.Env = os.Environ()
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ require.NoError(t, cmd.Run())
+
+ // Verify tctl status after login.
+ cmd = exec.CommandContext(ctx, filepath.Join(toolsDir, "tctl"), "status", "--insecure")
+ cmd.Env = os.Environ()
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ require.NoError(t, cmd.Run())
+
+ // Run version command to verify that login command executed auto update and
+ // tsh was upgraded to [v3.2.1].
+ cmd = exec.CommandContext(ctx, executable, "version")
+ out, err = cmd.Output()
+ require.NoError(t, err)
+
+ matches := pattern.FindStringSubmatch(string(out))
+ require.Len(t, matches, 2)
+ require.Equal(t, testVersions[1], matches[1])
+}
diff --git a/lib/autoupdate/tools/helper.go b/lib/autoupdate/tools/helper.go
index 79069c3d3c90b..3c078773ecaff 100644
--- a/lib/autoupdate/tools/helper.go
+++ b/lib/autoupdate/tools/helper.go
@@ -26,9 +26,20 @@ import (
"github.com/gravitational/trace"
+ "github.com/gravitational/teleport"
stacksignal "github.com/gravitational/teleport/lib/utils/signal"
)
+// Variables might to be overridden during compilation time for integration tests.
+var (
+ // version is the current version of the Teleport.
+ version = teleport.Version
+ // baseURL is CDN URL for downloading official Teleport packages.
+ baseURL = defaultBaseURL
+ // toolsDir is client tools directory for package download and installation.
+ toolsDir = ""
+)
+
// 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
@@ -36,13 +47,16 @@ import (
// 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 := Dir()
- if err != nil {
- slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
- return nil
+func CheckAndUpdateLocal(ctx context.Context, reExecArgs []string) error {
+ var err error
+ if toolsDir == "" {
+ if toolsDir, err = Dir(); err != nil {
+ slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
+ return nil
+ }
}
- updater := NewUpdater(toolsDir, currentVersion)
+
+ updater := NewUpdater(toolsDir, version, WithBaseURL(baseURL))
// 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.
@@ -51,7 +65,7 @@ func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error {
return trace.Wrap(err)
}
if reExec {
- return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
+ return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion, reExecArgs))
}
return nil
@@ -64,13 +78,15 @@ func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error {
// 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 := Dir()
- if err != nil {
- slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
- return nil
+func CheckAndUpdateRemote(ctx context.Context, proxy string, insecure bool, reExecArgs []string) error {
+ var err error
+ if toolsDir == "" {
+ if toolsDir, err = Dir(); err != nil {
+ slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
+ return nil
+ }
}
- updater := NewUpdater(toolsDir, currentVersion)
+ updater := NewUpdater(toolsDir, version, WithBaseURL(baseURL))
// 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.
//
@@ -81,13 +97,13 @@ func CheckAndUpdateRemote(ctx context.Context, currentVersion string, proxy stri
return trace.Wrap(err)
}
if reExec {
- return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
+ return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion, reExecArgs))
}
return nil
}
-func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string) error {
+func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string, args []string) error {
ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(ctx)
defer cancel()
// Download the version of client tools required by the cluster. This
@@ -99,7 +115,7 @@ func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string)
}
// Re-execute client tools with the correct version of client tools.
- code, err := updater.Exec()
+ code, err := updater.Exec(args)
if err != nil && !errors.Is(err, os.ErrNotExist) {
slog.DebugContext(ctx, "Failed to re-exec client tool", "error", err)
os.Exit(code)
diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go
index 8bad07c395391..9ca924b15d536 100644
--- a/lib/autoupdate/tools/updater.go
+++ b/lib/autoupdate/tools/updater.go
@@ -49,8 +49,8 @@ import (
const (
// teleportToolsVersionEnv is environment name for requesting specific version for update.
teleportToolsVersionEnv = "TELEPORT_TOOLS_VERSION"
- // baseURL is CDN URL for downloading official Teleport packages.
- baseURL = "https://cdn.teleport.dev"
+ // defaultBaseURL is CDN URL for downloading official Teleport packages.
+ defaultBaseURL = "https://cdn.teleport.dev"
// reservedFreeDisk is the predefined amount of free disk space (in bytes) required
// to remain available after downloading archives.
reservedFreeDisk = 10 * 1024 * 1024 // 10 Mb
@@ -61,7 +61,7 @@ const (
)
var (
- // // pattern is template for response on version command for client tools {tsh, tctl}.
+ // pattern is template for response on version command for client tools {tsh, tctl}.
pattern = regexp.MustCompile(`(?m)Teleport v(.*) git`)
)
@@ -109,7 +109,7 @@ func NewUpdater(toolsDir, localVersion string, options ...Option) *Updater {
tools: DefaultClientTools(),
toolsDir: toolsDir,
localVersion: localVersion,
- baseURL: baseURL,
+ baseURL: defaultBaseURL,
client: http.DefaultClient,
}
for _, option := range options {
@@ -155,31 +155,11 @@ func (u *Updater) CheckLocal() (version string, reExec bool, err error) {
return toolsVersion, true, nil
}
-// CheckRemote first checks the version set by the environment variable. If not set or disabled,
-// it checks against the Proxy Service to determine if client tools need updating by requesting
+// CheckRemote checks against the Proxy Service to determine if client tools need updating by requesting
// the `webapi/find` handler, which stores information about the required client tools version to
// operate with this cluster. It returns the semantic version that needs updating and whether
// re-execution is necessary, by re-execution flag we understand that update and re-execute is required.
func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bool) (version string, reExec bool, err error) {
- // Check if the user has requested a specific version of client tools.
- requestedVersion := os.Getenv(teleportToolsVersionEnv)
- switch requestedVersion {
- // The user has turned off any form of automatic updates.
- case "off":
- return "", false, nil
- // Requested version already the same as client version.
- case u.localVersion:
- return u.localVersion, false, nil
- // No requested version, we continue.
- case "":
- // Requested version that is not the local one.
- default:
- if _, err := semver.NewVersion(requestedVersion); err != nil {
- return "", false, trace.Wrap(err, "checking that request version is semantic")
- }
- return requestedVersion, true, nil
- }
-
certPool, err := x509.SystemCertPool()
if err != nil {
return "", false, trace.Wrap(err)
@@ -327,16 +307,20 @@ func (u *Updater) update(ctx context.Context, pkg packageURL, pkgName string) er
}
// Exec re-executes tool command with same arguments and environ variables.
-func (u *Updater) Exec() (int, error) {
+func (u *Updater) Exec(args []string) (int, error) {
path, err := toolName(u.toolsDir)
if err != nil {
return 0, trace.Wrap(err)
}
- // To prevent re-execution loop we have to disable update logic for re-execution.
+ // To prevent re-execution loop we have to disable update logic for re-execution,
+ // by unsetting current tools version env variable and setting it to "off".
+ if err := os.Unsetenv(teleportToolsVersionEnv); err != nil {
+ return 0, trace.Wrap(err)
+ }
env := append(os.Environ(), teleportToolsVersionEnv+"=off")
if runtime.GOOS == constants.WindowsOS {
- cmd := exec.Command(path, os.Args[1:]...)
+ cmd := exec.Command(path, args...)
cmd.Env = env
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -348,7 +332,7 @@ func (u *Updater) Exec() (int, error) {
return cmd.ProcessState.ExitCode(), nil
}
- if err := syscall.Exec(path, append([]string{path}, os.Args[1:]...), env); err != nil {
+ if err := syscall.Exec(path, append([]string{path}, args...), env); err != nil {
return 0, trace.Wrap(err)
}
@@ -415,7 +399,6 @@ func (u *Updater) downloadArchive(ctx context.Context, url string, f io.Writer)
return nil, trace.Wrap(err)
}
}
-
h := sha256.New()
// It is a little inefficient to download the file to disk and then re-load
// it into memory to unarchive later, but this is safer as it allows client
diff --git a/lib/client/api.go b/lib/client/api.go
index 6cc0caae15286..90c01e87384c7 100644
--- a/lib/client/api.go
+++ b/lib/client/api.go
@@ -709,7 +709,7 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error,
return trace.Wrap(err)
}
- if err := tools.CheckAndUpdateRemote(ctx, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(ctx, tc.WebProxyAddr, tc.InsecureSkipVerify, os.Args[1:]); err != nil {
return trace.Wrap(err)
}
diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go
index 0c5ebbc7887d9..5b1d3fa56676f 100644
--- a/tool/tctl/common/tctl.go
+++ b/tool/tctl/common/tctl.go
@@ -29,7 +29,6 @@ import (
"github.com/alecthomas/kingpin/v2"
"github.com/gravitational/trace"
- "github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/breaker"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/autoupdate/tools"
@@ -72,7 +71,7 @@ type CLICommand interface {
//
// distribution: name of the Teleport distribution
func Run(ctx context.Context, commands []CLICommand) {
- if err := tools.CheckAndUpdateLocal(ctx, teleport.Version); err != nil {
+ if err := tools.CheckAndUpdateLocal(ctx, os.Args[1:]); err != nil {
utils.FatalError(err)
}
diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go
index 582cce0fcd08d..87032aabba4d3 100644
--- a/tool/tsh/common/tsh.go
+++ b/tool/tsh/common/tsh.go
@@ -713,7 +713,7 @@ 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 {
- if err := tools.CheckAndUpdateLocal(ctx, teleport.Version); err != nil {
+ if err := tools.CheckAndUpdateLocal(ctx, args); err != nil {
return trace.Wrap(err)
}
@@ -1490,7 +1490,7 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {
case sessionsList.FullCommand():
err = onListSessions(&cf)
case login.FullCommand():
- err = onLogin(&cf)
+ err = onLogin(&cf, args...)
case logout.FullCommand():
err = onLogout(&cf)
case show.FullCommand():
@@ -1845,7 +1845,7 @@ func serializeVersion(format string, proxyVersion string, proxyPublicAddress str
}
// onLogin logs in with remote proxy and gets signed certificates
-func onLogin(cf *CLIConf) error {
+func onLogin(cf *CLIConf, reExecArgs ...string) error {
autoRequest := true
// special case: --request-roles=no disables auto-request behavior.
if cf.DesiredRoles == "no" {
@@ -1886,7 +1886,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 := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
}
@@ -1904,7 +1904,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 := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
@@ -1924,7 +1924,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 := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
@@ -2000,7 +2000,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 := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
}