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) } }