diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl index a3a3c00b83656..ea7228e5ef924 100644 --- a/api/types/installers/agentless-installer.sh.tmpl +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -12,6 +12,11 @@ set -o nounset . /etc/os-release + PACKAGE_LIST="{{ .TeleportPackage }}" + if [[ "{{ .AutomaticUpgrades }}" == "true" ]]; then + PACKAGE_LIST="${PACKAGE_LIST} {{ .TeleportPackage }}-updater" + fi + if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then # old versions of ubuntu require that keys get added by `apt-key add`, without # adding the key apt shows a key signing error when installing teleport. @@ -25,14 +30,14 @@ set -o nounset echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} {{ .RepoChannel }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null fi sudo apt-get update - sudo apt-get install -y {{ .TeleportPackage }} + sudo apt-get install -y ${PACKAGE_LIST} elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ]; then if [ "$ID" = "rhel" ]; then VERSION_ID=$(echo "$VERSION_ID" | sed 's/\..*//') # convert version numbers like '7.2' to only include the major version fi sudo yum-config-manager --add-repo \ "$(rpm --eval "https://yum.releases.teleport.dev/$ID/$VERSION_ID/Teleport/%{_arch}/{{ .RepoChannel }}/teleport.repo")" - sudo yum install -y {{ .TeleportPackage }} + sudo yum install -y ${PACKAGE_LIST} else echo "Unsupported distro: $ID" exit 1 diff --git a/api/types/installers/installer.sh.tmpl b/api/types/installers/installer.sh.tmpl index 1e095728ad2f7..864e85bf04745 100644 --- a/api/types/installers/installer.sh.tmpl +++ b/api/types/installers/installer.sh.tmpl @@ -22,6 +22,11 @@ on_azure() { . /etc/os-release + PACKAGE_LIST="{{ .TeleportPackage }} jq" + if [[ "{{ .AutomaticUpgrades }}" == "true" ]]; then + PACKAGE_LIST="${PACKAGE_LIST} {{ .TeleportPackage }}-updater" + fi + # old versions of ubuntu require that keys get added by `apt-key add`, without # adding the key apt shows a key signing error when installing teleport. LEGACY_UBUNTU=false @@ -41,14 +46,14 @@ on_azure() { echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} {{ .RepoChannel }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null fi sudo apt-get update - sudo apt-get install -y {{ .TeleportPackage }} jq + sudo apt-get install -y ${PACKAGE_LIST} elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ]; then if [ "$ID" = "rhel" ]; then VERSION_ID=$(echo "$VERSION_ID" | sed 's/\..*//') # convert version numbers like '7.2' to only include the major version fi sudo yum-config-manager --add-repo \ "$(rpm --eval "https://yum.releases.teleport.dev/$ID/$VERSION_ID/Teleport/%{_arch}/{{ .RepoChannel }}/teleport.repo")" - sudo yum install -y {{ .TeleportPackage }} jq + sudo yum install -y ${PACKAGE_LIST} else echo "Unsupported distro: $ID" exit 1 diff --git a/api/types/installers/installers.go b/api/types/installers/installers.go index 1317be807698c..55b11fcde9958 100644 --- a/api/types/installers/installers.go +++ b/api/types/installers/installers.go @@ -56,4 +56,7 @@ type Template struct { TeleportPackage string // RepoChannel is the repo's channel name to install. RepoChannel string + // AutomaticUpgrades indicates whether Automatic Upgrades are enabled or disabled. + // Its value is either `true` or `false`. + AutomaticUpgrades string } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index eb3de3aedb7f2..5bd80f5e27530 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1594,11 +1594,15 @@ func (h *Handler) installer(w http.ResponseWriter, r *http.Request, p httprouter repoChannel = stableCloudChannelRepo } + // TODO(marco): remove BuildType check when teleport-upgrade (oss) package is available in apt/yum repos. + automaticUpgrades := feats.AutomaticUpgrades && modules.GetModules().BuildType() == modules.BuildEnterprise + tmpl := installers.Template{ - PublicProxyAddr: h.PublicProxyAddr(), - MajorVersion: version, - TeleportPackage: teleportPackage, - RepoChannel: repoChannel, + PublicProxyAddr: h.PublicProxyAddr(), + MajorVersion: version, + TeleportPackage: teleportPackage, + RepoChannel: repoChannel, + AutomaticUpgrades: strconv.FormatBool(automaticUpgrades), } err = instTmpl.Execute(w, tmpl) return nil, trace.Wrap(err) diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 8bda547d3aaa7..b2cc9a46d6866 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -2432,20 +2432,22 @@ func TestPingAutomaticUpgrades(t *testing.T) { // TestInstallerRepoChannel ensures the returned installer script has the proper repo channel func TestInstallerRepoChannel(t *testing.T) { + s := newWebSuiteWithConfig(t, webSuiteConfig{ + authPreferenceSpec: &types.AuthPreferenceSpecV2{ + Type: constants.Local, + SecondFactor: constants.SecondFactorOn, + Webauthn: &types.Webauthn{RPID: "localhost"}, + }, + }) + + wc := s.client(t) t.Run("documented variables are injected", func(t *testing.T) { - s := newWebSuiteWithConfig(t, webSuiteConfig{ - authPreferenceSpec: &types.AuthPreferenceSpecV2{ - Type: constants.Local, - SecondFactor: constants.SecondFactorOn, - Webauthn: &types.Webauthn{RPID: "localhost"}, - }, - }) - wc := s.client(t) // Variables documented here: https://goteleport.com/docs/server-access/guides/ec2-discovery/#step-67-optional-customize-the-default-installer-script err := s.server.Auth().SetInstaller(s.ctx, types.MustNewInstallerV1("custom", `#!/usr/bin/env bash echo {{ .PublicProxyAddr }} echo Teleport-{{ .MajorVersion }} echo Repository Channel: {{ .RepoChannel }} +echo AutomaticUpgrades: {{ .AutomaticUpgrades }} `)) require.NoError(t, err) @@ -2457,21 +2459,15 @@ echo Repository Channel: {{ .RepoChannel }} // Variables must be injected require.Contains(t, responseString, "echo Teleport-v") require.Contains(t, responseString, "echo Repository Channel: stable/v") + require.Contains(t, responseString, "echo AutomaticUpgrades: false") }) t.Run("cloud with automatic upgrades", func(t *testing.T) { - modules.SetTestModules(t, &modules.TestModules{TestFeatures: modules.Features{ - Cloud: true, - AutomaticUpgrades: true, - }}) - - s := newWebSuiteWithConfig(t, webSuiteConfig{ - authPreferenceSpec: &types.AuthPreferenceSpecV2{ - Type: constants.Local, - SecondFactor: constants.SecondFactorOn, - Webauthn: &types.Webauthn{RPID: "localhost"}, - }, - }) - wc := s.client(t) + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + Cloud: true, + AutomaticUpgrades: true, + }}) t.Run("default-installer", func(t *testing.T) { re, err := wc.Get(s.ctx, wc.Endpoint("webapi", "scripts", "installer", "default-installer"), url.Values{}) @@ -2482,6 +2478,12 @@ echo Repository Channel: {{ .RepoChannel }} // The repo's channel to use is stable/cloud require.Contains(t, responseString, "stable/cloud") require.NotContains(t, responseString, "stable/v") + require.Contains(t, responseString, ""+ + " PACKAGE_LIST=\"teleport-ent jq\"\n"+ + " if [[ \"true\" == \"true\" ]]; then\n"+ + " PACKAGE_LIST=\"${PACKAGE_LIST} teleport-ent-updater\"\n"+ + " fi\n", + ) }) t.Run("default-agentless-installer", func(t *testing.T) { re, err := wc.Get(s.ctx, wc.Endpoint("webapi", "scripts", "installer", "default-agentless-installer"), url.Values{}) @@ -2492,6 +2494,12 @@ echo Repository Channel: {{ .RepoChannel }} // The repo's channel to use is stable/cloud require.Contains(t, responseString, "stable/cloud") require.NotContains(t, responseString, "stable/v") + require.Contains(t, responseString, ""+ + " PACKAGE_LIST=\"teleport-ent\"\n"+ + " if [[ \"true\" == \"true\" ]]; then\n"+ + " PACKAGE_LIST=\"${PACKAGE_LIST} teleport-ent-updater\"\n"+ + " fi\n", + ) }) }) t.Run("cloud without automatic upgrades", func(t *testing.T) { @@ -2500,15 +2508,6 @@ echo Repository Channel: {{ .RepoChannel }} AutomaticUpgrades: false, }}) - s := newWebSuiteWithConfig(t, webSuiteConfig{ - authPreferenceSpec: &types.AuthPreferenceSpecV2{ - Type: constants.Local, - SecondFactor: constants.SecondFactorOn, - Webauthn: &types.Webauthn{RPID: "localhost"}, - }, - }) - wc := s.client(t) - t.Run("default-installer", func(t *testing.T) { re, err := wc.Get(s.ctx, wc.Endpoint("webapi", "scripts", "installer", "default-installer"), url.Values{}) require.NoError(t, err) diff --git a/lib/web/join_tokens.go b/lib/web/join_tokens.go index f31b0ff275457..dc4ffc831fff7 100644 --- a/lib/web/join_tokens.go +++ b/lib/web/join_tokens.go @@ -78,6 +78,13 @@ type scriptSettings struct { joinMethod string databaseInstallMode bool stableCloudChannelRepo bool + installUpdater bool +} + +// automaticUpgrades returns whether automaticUpgrades should be enabled. +func automaticUpgrades(features proto.Features) bool { + // TODO(marco): remove BuildType check when teleport-updater (oss) package is available in apt/yum repos. + return features.AutomaticUpgrades && modules.GetModules().BuildType() == modules.BuildEnterprise } func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) { @@ -209,6 +216,7 @@ func (h *Handler) getNodeJoinScriptHandle(w http.ResponseWriter, r *http.Request appInstallMode: false, joinMethod: r.URL.Query().Get("method"), stableCloudChannelRepo: useStableCloudChannelRepo, + installUpdater: automaticUpgrades(h.ClusterFeatures), } script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) @@ -253,6 +261,7 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request, appName: name, appURI: uri, stableCloudChannelRepo: useStableCloudChannelRepo, + installUpdater: automaticUpgrades(h.ClusterFeatures), } script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) @@ -280,6 +289,7 @@ func (h *Handler) getDatabaseJoinScriptHandle(w http.ResponseWriter, r *http.Req token: params.ByName("token"), databaseInstallMode: true, stableCloudChannelRepo: useStableCloudChannelRepo, + installUpdater: automaticUpgrades(h.ClusterFeatures), } script, err := getJoinScript(r.Context(), settings, h.GetProxyClient()) @@ -406,6 +416,7 @@ func getJoinScript(ctx context.Context, settings scriptSettings, m nodeAPIGetter "caPins": strings.Join(caPins, ","), "packageName": packageName, "repoChannel": repoChannel, + "installUpdater": strconv.FormatBool(settings.installUpdater), "version": version, "appInstallMode": strconv.FormatBool(settings.appInstallMode), "appName": settings.appName, diff --git a/lib/web/join_tokens_test.go b/lib/web/join_tokens_test.go index 0b01c53ccdc1d..680b956749f51 100644 --- a/lib/web/join_tokens_test.go +++ b/lib/web/join_tokens_test.go @@ -926,6 +926,34 @@ func TestJoinScript(t *testing.T) { }) t.Run("using repo", func(t *testing.T) { + t.Run("installUpdater set to true, list of packages must include updater package", func(t *testing.T) { + script, err := getJoinScript(context.Background(), scriptSettings{token: validToken, installUpdater: true}, m) + require.NoError(t, err) + + require.Contains(t, script, ""+ + " PACKAGE_LIST=${TELEPORT_PACKAGE_NAME}\n"+ + " # (warning): This expression is constant. Did you forget the $ on a variable?\n"+ + " # Disabling the warning above because expression is templated.\n"+ + " # shellcheck disable=SC2050\n"+ + " if [[ \"true\" == \"true\" ]]; then\n"+ + " PACKAGE_LIST=\"${PACKAGE_LIST} ${TELEPORT_PACKAGE_NAME}-updater\"\n"+ + " fi\n", + ) + }) + t.Run("installUpdater set to false, list of packages must not include updater package", func(t *testing.T) { + script, err := getJoinScript(context.Background(), scriptSettings{token: validToken, installUpdater: false}, m) + require.NoError(t, err) + + require.Contains(t, script, ""+ + " PACKAGE_LIST=${TELEPORT_PACKAGE_NAME}\n"+ + " # (warning): This expression is constant. Did you forget the $ on a variable?\n"+ + " # Disabling the warning above because expression is templated.\n"+ + " # shellcheck disable=SC2050\n"+ + " if [[ \"false\" == \"true\" ]]; then\n"+ + " PACKAGE_LIST=\"${PACKAGE_LIST} ${TELEPORT_PACKAGE_NAME}-updater\"\n"+ + " fi\n", + ) + }) t.Run("cloud and automatic upgrades", func(t *testing.T) { script, err := getJoinScript(context.Background(), scriptSettings{token: validToken, stableCloudChannelRepo: true}, m) require.NoError(t, err) @@ -949,6 +977,44 @@ func TestJoinScript(t *testing.T) { } +func TestAutomaticUpgrades(t *testing.T) { + t.Run("enterprise and automatic upgrades enabled", func(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + AutomaticUpgrades: true, + }, + }) + + got := automaticUpgrades(*modules.GetModules().Features().ToProto()) + require.True(t, got) + }) + t.Run("enterprise but automatic upgrades disabled", func(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + AutomaticUpgrades: false, + }, + }) + + got := automaticUpgrades(*modules.GetModules().Features().ToProto()) + require.False(t, got) + }) + + // There's no `teleport-updater` (oss) package yet, so even if automatic upgrades are enabled it must return false. + t.Run("automatic upgrades enabled but build is OSS", func(t *testing.T) { + modules.SetTestModules(t, &modules.TestModules{ + TestBuildType: modules.BuildEnterprise, + TestFeatures: modules.Features{ + AutomaticUpgrades: false, + }, + }) + + got := automaticUpgrades(*modules.GetModules().Features().ToProto()) + require.False(t, got) + }) +} + func TestIsSameAzureRuleSet(t *testing.T) { tests := []struct { name string diff --git a/lib/web/scripts/node-join/install.sh b/lib/web/scripts/node-join/install.sh index b4a9ea0f57164..b435791245f40 100755 --- a/lib/web/scripts/node-join/install.sh +++ b/lib/web/scripts/node-join/install.sh @@ -851,6 +851,14 @@ install_from_repo() { REPO_CHANNEL=stable/v"${TELEPORT_VERSION//.*/}" fi + PACKAGE_LIST=${TELEPORT_PACKAGE_NAME} + # (warning): This expression is constant. Did you forget the $ on a variable? + # Disabling the warning above because expression is templated. + # shellcheck disable=SC2050 + if [[ "{{.installUpdater}}" == "true" ]]; then + PACKAGE_LIST="${PACKAGE_LIST} ${TELEPORT_PACKAGE_NAME}-updater" + fi + # Populate $ID, $VERSION_ID, $VERSION_CODENAME and other env vars identifying the OS. # shellcheck disable=SC1091 . /etc/os-release @@ -872,7 +880,7 @@ install_from_repo() { https://apt.releases.teleport.dev/${ID} ${VERSION_CODENAME} ${REPO_CHANNEL}" > /etc/apt/sources.list.d/teleport.list fi apt-get update - apt-get install -y ${TELEPORT_PACKAGE_NAME} + apt-get install -y ${PACKAGE_LIST} elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ] || [ "$ID" = "centos" ] ; then if [ "$ID" = "rhel" ]; then VERSION_ID="${VERSION_ID//.*/}" # convert version numbers like '7.2' to only include the major version @@ -885,7 +893,7 @@ install_from_repo() { # See: https://github.com/gravitational/teleport/issues/22581 yum --disablerepo="*" --enablerepo="teleport" clean metadata - yum install -y ${TELEPORT_PACKAGE_NAME} + yum install -y ${PACKAGE_LIST} else echo "Unsupported distro: $ID" exit 1