diff --git a/integration/autoupdate/tools/updater/modules.go b/integration/autoupdate/tools/updater/modules.go index 28d03f42333df..017e5adb3cb70 100644 --- a/integration/autoupdate/tools/updater/modules.go +++ b/integration/autoupdate/tools/updater/modules.go @@ -22,6 +22,7 @@ import ( "context" "crypto" "fmt" + "os" "time" "github.com/gravitational/trace" @@ -35,8 +36,12 @@ import ( "github.com/gravitational/teleport/lib/tlsca" ) -// TestPassword is password generated during the test to login in test cluster. -const TestPassword = "UPDATER_TEST_PASSWORD" +const ( + // TestPassword is env var used for setting password generated during the test to login in test cluster. + TestPassword = "UPDATER_TEST_PASSWORD" + // TestBuild is env var for setting test build type during the test. + TestBuild = "UPDATER_TEST_BUILD" +) var ( version = teleport.Version @@ -54,17 +59,20 @@ func (p *TestModules) GetSuggestedAccessLists(context.Context, *tlsca.Identity, // BuildType returns build type (OSS or Enterprise) func (p *TestModules) BuildType() string { + if build := os.Getenv(TestBuild); build != "" { + return build + } return "CLI" } // IsEnterpriseBuild returns false for [TestModules]. func (p *TestModules) IsEnterpriseBuild() bool { - return false + return os.Getenv(TestBuild) == modules.BuildEnterprise } // IsOSSBuild returns false for [TestModules]. func (p *TestModules) IsOSSBuild() bool { - return false + return os.Getenv(TestBuild) == modules.BuildOSS } // LicenseExpiry returns the expiry date of the enterprise license, if applicable. diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go index 0c05f2524caf0..21f2962d75768 100644 --- a/integration/autoupdate/tools/updater_test.go +++ b/integration/autoupdate/tools/updater_test.go @@ -34,7 +34,10 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/integration/autoupdate/tools/updater" + "github.com/gravitational/teleport/lib/autoupdate" "github.com/gravitational/teleport/lib/autoupdate/tools" + "github.com/gravitational/teleport/lib/modules" ) var ( @@ -216,3 +219,49 @@ func TestUpdateInterruptSignal(t *testing.T) { } assert.Contains(t, output.String(), "Update progress:") } + +// TestUpdateForOSSBuild verifies the update logic for AGPL editions of Teleport requires +// base URL environment variable. +func TestUpdateForOSSBuild(t *testing.T) { + t.Setenv(types.HomeEnvVar, t.TempDir()) + ctx := context.Background() + + // Enable OSS build. + t.Setenv(updater.TestBuild, modules.BuildOSS) + + // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. + updater := tools.NewUpdater( + toolsDir, + testVersions[0], + tools.WithBaseURL(baseURL), + ) + err := updater.Update(ctx, testVersions[0]) + require.NoError(t, err) + + // Verify that requested update is ignored by OSS build and version wasn't updated. + cmd := exec.CommandContext(ctx, filepath.Join(toolsDir, "tsh"), "version") + cmd.Env = append( + os.Environ(), + fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), + ) + out, err := cmd.Output() + require.NoError(t, err) + + matches := pattern.FindStringSubmatch(string(out)) + require.Len(t, matches, 2) + require.Equal(t, testVersions[0], matches[1]) + + // Next update is set with the base URL env variable, must download new version. + t.Setenv(autoupdate.BaseURLEnvVar, baseURL) + cmd = exec.CommandContext(ctx, filepath.Join(toolsDir, "tsh"), "version") + cmd.Env = append( + os.Environ(), + fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), + ) + 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 f7d3e691b2ef4..c05618e430a92 100644 --- a/lib/autoupdate/tools/helper.go +++ b/lib/autoupdate/tools/helper.go @@ -31,6 +31,11 @@ import ( stacksignal "github.com/gravitational/teleport/lib/utils/signal" ) +// warnMessageOSSBuild is warning exposed to the user that build type without base url is disabled. +const warnMessageOSSBuild = "Client tools updates are disabled because the server is licensed under AGPL " + + "but Teleport-distributed binaries are licensed under Community Edition. To use Community Edition " + + "builds or custom binaries, set the 'TELEPORT_CDN_BASE_URL' environment variable." + // Variables might to be overridden during compilation time for integration tests. var ( // version is the current version of the Teleport. @@ -86,6 +91,7 @@ func CheckAndUpdateRemote(ctx context.Context, proxy string, insecure bool, reEx slog.WarnContext(ctx, "Client tools update is disabled", "error", err) return nil } + // Overrides default base URL for custom CDN for downloading updates. if envBaseURL := os.Getenv(autoupdate.BaseURLEnvVar); envBaseURL != "" { baseURL = envBaseURL @@ -110,6 +116,11 @@ func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string, // is required if the user passed in the TELEPORT_TOOLS_VERSION // explicitly. err := updater.UpdateWithLock(ctxUpdate, toolsVersion) + if err != nil && errors.Is(err, errNoBaseURL) { + // If base URL wasn't defined we have to cancel update and re-execution with warning. + slog.WarnContext(ctx, warnMessageOSSBuild) + return nil + } if err != nil && !errors.Is(err, context.Canceled) { return trace.Wrap(err) } diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go index b148be735aedb..d18689b61bb93 100644 --- a/lib/autoupdate/tools/updater.go +++ b/lib/autoupdate/tools/updater.go @@ -409,11 +409,6 @@ func (u *Updater) downloadHash(ctx context.Context, url string) ([]byte, error) // downloadArchive downloads the archive package by `url` and writes content to the writer interface, // return calculated sha256 hash sum of the content. func (u *Updater) downloadArchive(ctx context.Context, url string, f io.Writer) ([]byte, error) { - // Display a progress bar before initiating the update request to inform the user that - // an update is in progress, allowing them the option to cancel before actual response - // which might be delayed with slow internet connection or complete isolation to CDN. - pw, finish := newProgressWriter(10) - defer finish() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, trace.Wrap(err) @@ -429,6 +424,8 @@ func (u *Updater) downloadArchive(ctx context.Context, url string, f io.Writer) if resp.StatusCode != http.StatusOK { return nil, trace.BadParameter("bad status when downloading archive: %v", resp.StatusCode) } + pw, finish := newProgressWriter(10) + defer finish() if resp.ContentLength != -1 { if err := checkFreeSpace(u.toolsDir, uint64(resp.ContentLength)); err != nil { diff --git a/lib/autoupdate/tools/utils.go b/lib/autoupdate/tools/utils.go index eb9e5f7ad8a9f..66cfd7633f09a 100644 --- a/lib/autoupdate/tools/utils.go +++ b/lib/autoupdate/tools/utils.go @@ -40,6 +40,8 @@ import ( "github.com/gravitational/teleport/lib/utils" ) +var errNoBaseURL = errors.New("baseURL is not defined") + // Dir returns the path to client tools in $TELEPORT_HOME/bin. func Dir() (string, error) { home := os.Getenv(types.HomeEnvVar) @@ -128,6 +130,11 @@ type packageURL struct { // teleportPackageURLs returns the URL for the Teleport archive to download. func teleportPackageURLs(uriTmpl string, baseURL, version string) ([]packageURL, error) { + envBaseURL := os.Getenv(autoupdate.BaseURLEnvVar) + if modules.GetModules().BuildType() == modules.BuildOSS && envBaseURL == "" { + return nil, errNoBaseURL + } + var flags autoupdate.InstallFlags m := modules.GetModules() if m.IsBoringBinary() {