From e2d79a4ff8422fe05ed819b45f176e62308ad5ba Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Sun, 15 Dec 2024 21:24:50 -0500 Subject: [PATCH 01/13] wip --- lib/autoupdate/agent/telemetry.go | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/autoupdate/agent/telemetry.go diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go new file mode 100644 index 0000000000000..4ecdd9b3bb91e --- /dev/null +++ b/lib/autoupdate/agent/telemetry.go @@ -0,0 +1,39 @@ +/* + * 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 agent + +import ( + "os" + "path/filepath" + "strings" + + "github.com/gravitational/trace" +) + +func IsEnabled() (bool, error) { + teleportPath, err := os.Readlink("/proc/self/exe") + if err != nil { + return false, trace.Wrap(err, "cannot find Teleport binary") + } + if !strings.HasPrefix(teleportPath, teleportOptDir) { + return false, nil + } + systemDir := filepath.Join(teleportOptDir, systemNamespace) + return !strings.HasPrefix(teleportPath, systemDir), nil +} From 9b85e0ff6b3a1d067c0b940a1f939163df61f597 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Sun, 15 Dec 2024 22:42:37 -0500 Subject: [PATCH 02/13] telemetry --- lib/service/service.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/service/service.go b/lib/service/service.go index 7fd997e7234f0..1f2f1e4c3c6c5 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -98,6 +98,7 @@ import ( "github.com/gravitational/teleport/lib/auth/storage" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/automaticupgrades" + autoupdate "github.com/gravitational/teleport/lib/autoupdate/agent" "github.com/gravitational/teleport/lib/autoupdate/rollout" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/dynamo" @@ -1235,6 +1236,17 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { upgraderKind = "" } + // If the new auto-updater is enabled, it superceeds the old one. + ok, err := autoupdate.IsEnabled() + if err != nil { + process.logger.WarnContext(process.ExitContext(), "Failed to determine if auto-updates are enabled.", "error", err) + } else if ok { + // If this is a teleport-update managed installation, the version + // managed by the timer will always match the installed version of teleport. + upgraderKind = "teleport-update" + upgraderVersion = "v" + teleport.Version + } + // Instances deployed using the AWS OIDC integration are automatically updated // by the proxy. The instance heartbeat should properly reflect that. externalUpgrader := upgraderKind From d97c35ac39d7071a116996e497d9c46f6fbe4050 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Sun, 15 Dec 2024 22:50:05 -0500 Subject: [PATCH 03/13] abs --- lib/autoupdate/agent/telemetry.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go index 4ecdd9b3bb91e..b671132c679ac 100644 --- a/lib/autoupdate/agent/telemetry.go +++ b/lib/autoupdate/agent/telemetry.go @@ -31,9 +31,14 @@ func IsEnabled() (bool, error) { if err != nil { return false, trace.Wrap(err, "cannot find Teleport binary") } - if !strings.HasPrefix(teleportPath, teleportOptDir) { + updaterBasePath := filepath.Clean(teleportOptDir) + "/" + absPath, err := filepath.Abs(teleportPath) + if err != nil { + return false, trace.Wrap(err, "cannot get absolute path for Teleport binary") + } + if !strings.HasPrefix(absPath, updaterBasePath) { return false, nil } systemDir := filepath.Join(teleportOptDir, systemNamespace) - return !strings.HasPrefix(teleportPath, systemDir), nil + return !strings.HasPrefix(absPath, systemDir), nil } From ab5fecfc636da15231a24462b523e5d8772bd67f Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Sun, 15 Dec 2024 23:18:19 -0500 Subject: [PATCH 04/13] fix --- api/types/maintenance.go | 6 ++++- lib/auth/auth.go | 2 +- lib/autoupdate/agent/telemetry.go | 4 ++- lib/service/service.go | 42 ++++++++++++++++--------------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/api/types/maintenance.go b/api/types/maintenance.go index 31a48472e6aa8..318cabe7df73d 100644 --- a/api/types/maintenance.go +++ b/api/types/maintenance.go @@ -26,13 +26,17 @@ import ( ) const ( - // UpgraderKindKuberController is a short name used to identify the kube-controller-based + // UpgraderKindKubeController is a short name used to identify the kube-controller-based // external upgrader variant. UpgraderKindKubeController = "kube" // UpgraderKindSystemdUnit is a short name used to identify the systemd-unit-based // external upgrader variant. UpgraderKindSystemdUnit = "unit" + + // UpgraderKindTeleportUpdate is a short name used to identify the teleport-update + // external upgrader variant. + UpgraderKindTeleportUpdate = "updater" ) var validWeekdays = [7]time.Weekday{ diff --git a/lib/auth/auth.go b/lib/auth/auth.go index aef1a77ed2564..f83b20d993466 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -6634,7 +6634,7 @@ func (a *Server) ExportUpgradeWindows(ctx context.Context, req proto.ExportUpgra } switch req.UpgraderKind { - case "": + case "", types.UpgraderKindTeleportUpdate: rsp.CanonicalSchedule = cached.CanonicalSchedule.Clone() case types.UpgraderKindKubeController: rsp.KubeControllerSchedule = cached.KubeControllerSchedule diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go index b671132c679ac..df321f6c791cf 100644 --- a/lib/autoupdate/agent/telemetry.go +++ b/lib/autoupdate/agent/telemetry.go @@ -26,7 +26,9 @@ import ( "github.com/gravitational/trace" ) -func IsEnabled() (bool, error) { +// IsActive returns true if the local Teleport binary is managed by teleport-update. +// Note that true may be returned even if auto-updates is disabled or the version is pinned. +func IsActive() (bool, error) { teleportPath, err := os.Readlink("/proc/self/exe") if err != nil { return false, trace.Wrap(err, "cannot find Teleport binary") diff --git a/lib/service/service.go b/lib/service/service.go index 1f2f1e4c3c6c5..b48db6ee73cc4 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1236,8 +1236,8 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { upgraderKind = "" } - // If the new auto-updater is enabled, it superceeds the old one. - ok, err := autoupdate.IsEnabled() + // If the installation is managed by teleport-update, it supersedes the teleport-upgrader script. + ok, err := autoupdate.IsActive() if err != nil { process.logger.WarnContext(process.ExitContext(), "Failed to determine if auto-updates are enabled.", "error", err) } else if ok { @@ -1285,7 +1285,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { process.logger.WarnContext(process.ExitContext(), "Use of external upgraders on control-plane instances is not recommended.") } - if upgraderKind == "unit" { + if upgraderKind == types.UpgraderKindSystemdUnit { process.RegisterFunc("autoupdates.endpoint.export", func() error { conn, err := waitForInstanceConnector(process, process.logger) if err != nil { @@ -1315,26 +1315,28 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { }) } - driver, err := uw.NewDriver(upgraderKind) - if err != nil { - return nil, trace.Wrap(err) - } + if upgraderKind != types.UpgraderKindTeleportUpdate { + driver, err := uw.NewDriver(upgraderKind) + if err != nil { + return nil, trace.Wrap(err) + } - exporter, err := uw.NewExporter(uw.ExporterConfig[inventory.DownstreamSender]{ - Driver: driver, - ExportFunc: process.exportUpgradeWindows, - AuthConnectivitySentinel: process.inventoryHandle.Sender(), - }) - if err != nil { - return nil, trace.Wrap(err) - } + exporter, err := uw.NewExporter(uw.ExporterConfig[inventory.DownstreamSender]{ + Driver: driver, + ExportFunc: process.exportUpgradeWindows, + AuthConnectivitySentinel: process.inventoryHandle.Sender(), + }) + if err != nil { + return nil, trace.Wrap(err) + } - process.RegisterCriticalFunc("upgradeewindow.export", exporter.Run) - process.OnExit("upgradewindow.export.stop", func(_ interface{}) { - exporter.Close() - }) + process.RegisterCriticalFunc("upgradeewindow.export", exporter.Run) + process.OnExit("upgradewindow.export.stop", func(_ interface{}) { + exporter.Close() + }) - process.logger.InfoContext(process.ExitContext(), "Configured upgrade window exporter for external upgrader.", "kind", upgraderKind) + process.logger.InfoContext(process.ExitContext(), "Configured upgrade window exporter for external upgrader.", "kind", upgraderKind) + } } serviceStarted := false From 78d047a5651964078e8d426ccc7451e9c2684271 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Mon, 16 Dec 2024 14:56:40 -0500 Subject: [PATCH 05/13] tests --- lib/service/service.go | 16 +++++++--- .../upgradewindow/upgradewindow.go | 32 ++++++++++++++++--- .../upgradewindow/upgradewindow_test.go | 14 ++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/service/service.go b/lib/service/service.go index b48db6ee73cc4..eb98f63552965 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1285,7 +1285,16 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { process.logger.WarnContext(process.ExitContext(), "Use of external upgraders on control-plane instances is not recommended.") } - if upgraderKind == types.UpgraderKindSystemdUnit { + switch upgraderKind { + case types.UpgraderKindTeleportUpdate: + driver, err := uw.NewSystemdUnitDriver(uw.SystemdUnitDriverConfig{}) + if err != nil { + return nil, trace.Wrap(err) + } + if err := driver.ForceNOP(process.ExitContext()); err != nil { + return nil, trace.Wrap(err) + } + case types.UpgraderKindSystemdUnit: process.RegisterFunc("autoupdates.endpoint.export", func() error { conn, err := waitForInstanceConnector(process, process.logger) if err != nil { @@ -1313,9 +1322,8 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { process.logger.InfoContext(process.ExitContext(), "Exported autoupdates endpoint.", "addr", resolverAddr.String()) return nil }) - } - - if upgraderKind != types.UpgraderKindTeleportUpdate { + fallthrough + default: driver, err := uw.NewDriver(upgraderKind) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/versioncontrol/upgradewindow/upgradewindow.go b/lib/versioncontrol/upgradewindow/upgradewindow.go index 7ec223e352ef1..4c1f044a22d87 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow.go @@ -44,6 +44,9 @@ const ( // unitScheduleFile is the name of the file to which the unit schedule is exported. unitScheduleFile = "schedule" + + // scheduleNOP is the name of the no-op schedule. + scheduleNOP = "nop" ) // ExportFunc represents the ExportUpgradeWindows rpc exposed by auth servers. @@ -313,6 +316,11 @@ type Driver interface { // called if teleport experiences prolonged loss of auth connectivity, which may be an indicator // that the control plane has been upgraded s.t. this agent is no longer compatible. Reset(ctx context.Context) error + + // ForceNOP sets the NOP schedule, ensuring that updates do not happen. + // This schedule was originally for testing, but now it also ensures that + // the teleport-update binary can disable all versions of the teleport-upgrader script. + ForceNOP(ctx context.Context) error } // NewDriver sets up a new export driver corresponding to the specified upgrader kind. @@ -361,7 +369,15 @@ func (e *kubeDriver) Kind() string { } func (e *kubeDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsResponse) error { - if rsp.KubeControllerSchedule == "" { + return trace.Wrap(e.setSchedule(ctx, rsp.KubeControllerSchedule)) +} + +func (e *kubeDriver) ForceNOP(ctx context.Context) error { + return trace.Wrap(e.setSchedule(ctx, scheduleNOP)) +} + +func (e *kubeDriver) setSchedule(ctx context.Context, schedule string) error { + if schedule == "" { return e.Reset(ctx) } @@ -369,7 +385,7 @@ func (e *kubeDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsRes // backend.KeyFromString is intentionally used here instead of backend.NewKey // because existing backend items were persisted without the leading /. Key: backend.KeyFromString(kubeSchedKey), - Value: []byte(rsp.KubeControllerSchedule), + Value: []byte(schedule), }) return trace.Wrap(err) @@ -411,7 +427,15 @@ func (e *systemdDriver) Kind() string { } func (e *systemdDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsResponse) error { - if len(rsp.SystemdUnitSchedule) == 0 { + return trace.Wrap(e.setSchedule(ctx, rsp.SystemdUnitSchedule)) +} + +func (e *systemdDriver) ForceNOP(ctx context.Context) error { + return trace.Wrap(e.setSchedule(ctx, scheduleNOP)) +} + +func (e *systemdDriver) setSchedule(ctx context.Context, schedule string) error { + if len(schedule) == 0 { // treat an empty schedule value as equivalent to a reset return e.Reset(ctx) } @@ -423,7 +447,7 @@ func (e *systemdDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindows } // export schedule file. if created it is set to 644, which is reasonable for a sensitive but non-secret config value. - if err := os.WriteFile(e.scheduleFile(), []byte(rsp.SystemdUnitSchedule), defaults.FilePermissions); err != nil { + if err := os.WriteFile(e.scheduleFile(), []byte(schedule), defaults.FilePermissions); err != nil { return trace.Errorf("failed to write schedule file: %v", err) } diff --git a/lib/versioncontrol/upgradewindow/upgradewindow_test.go b/lib/versioncontrol/upgradewindow/upgradewindow_test.go index 7b724708652f4..cff6aef4207c4 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow_test.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow_test.go @@ -27,6 +27,7 @@ import ( "testing" "time" + "github.com/gravitational/trace" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/client/proto" @@ -173,6 +174,15 @@ func TestSystemdUnitDriver(t *testing.T) { require.Equal(t, "fake-schedule-3", string(sb)) + // verify ForceNOP + err = driver.ForceNOP(ctx) + require.NoError(t, err) + + sb, err = os.ReadFile(schedPath) + require.NoError(t, err) + + require.Equal(t, scheduleNOP, string(sb)) + // verify that an empty schedule value is treated equivalent to a reset err = driver.Sync(ctx, proto.ExportUpgradeWindowsResponse{}) require.NoError(t, err) @@ -209,6 +219,10 @@ func (d *fakeDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsRes return nil } +func (d *fakeDriver) ForceNOP(ctx context.Context) error { + return trace.NotImplemented("force-nop not used by exporter") +} + func (d *fakeDriver) Reset(ctx context.Context) error { d.mu.Lock() defer d.mu.Unlock() From 0c36daa83b46f92eb9fa01dbd96db58ff9eb3358 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Mon, 13 Jan 2025 14:30:49 -0500 Subject: [PATCH 06/13] Disable deprecated timer --- lib/autoupdate/agent/process.go | 23 ++++++++- lib/autoupdate/agent/setup.go | 51 ++++++++++++++++--- lib/service/service.go | 5 +- .../upgradewindow/upgradewindow.go | 14 ++--- .../upgradewindow/upgradewindow_test.go | 8 +-- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/lib/autoupdate/agent/process.go b/lib/autoupdate/agent/process.go index 6a13d2e0687bf..51a2f62cd9bff 100644 --- a/lib/autoupdate/agent/process.go +++ b/lib/autoupdate/agent/process.go @@ -278,11 +278,15 @@ func (s SystemdService) Enable(ctx context.Context, now bool) error { } // Disable the systemd service. -func (s SystemdService) Disable(ctx context.Context) error { +func (s SystemdService) Disable(ctx context.Context, now bool) error { if err := s.checkSystem(ctx); err != nil { return trace.Wrap(err) } - code := s.systemctl(ctx, slog.LevelInfo, "disable", s.ServiceName) + args := []string{"disable", s.ServiceName} + if now { + args = append(args, "--now") + } + code := s.systemctl(ctx, slog.LevelInfo, args...) if code != 0 { return trace.Errorf("unable to disable systemd service") } @@ -312,6 +316,21 @@ func (s SystemdService) IsEnabled(ctx context.Context) (bool, error) { return false, nil } +// IsPresent returns true if the service exists. +func (s SystemdService) IsPresent(ctx context.Context) (bool, error) { + if err := s.checkSystem(ctx); err != nil { + return false, trace.Wrap(err) + } + code := s.systemctl(ctx, slog.LevelDebug, "list-unit-files", "--quiet", s.ServiceName) + switch { + case code < 0: + return false, trace.Errorf("unable to determine if systemd service %s is present", s.ServiceName) + case code == 0: + return true, nil + } + return false, nil +} + // checkSystem returns an error if the system is not compatible with this process manager. func (s SystemdService) checkSystem(ctx context.Context) error { _, err := os.Stat("/run/systemd/system") diff --git a/lib/autoupdate/agent/setup.go b/lib/autoupdate/agent/setup.go index c2575e0cc1e5f..a878c73e860dc 100644 --- a/lib/autoupdate/agent/setup.go +++ b/lib/autoupdate/agent/setup.go @@ -48,6 +48,11 @@ const ( systemNamespace = "system" ) +const ( + // deprecatedTimerName is the timer for the deprecated upgrader should be disabled on setup. + deprecatedTimerName = "teleport-upgrade.timer" +) + const ( updateServiceTemplate = `# teleport-update # DO NOT EDIT THIS FILE @@ -105,7 +110,7 @@ type Namespace struct { versionsDir string // serviceFile for the Teleport systemd service (ns: /etc/systemd/system/teleport_myns.service) serviceFile string - // configFile for Teleport config (ns: /opt/teleport/myns/etc/teleport.yaml) + // configFile for Teleport config (ns: /etc/teleport_myns.yaml) configFile string // pidFile for Teleport (ns: /run/teleport_myns.pid) pidFile string @@ -211,16 +216,33 @@ func (ns *Namespace) Setup(ctx context.Context) error { if err != nil { return trace.Wrap(err, "failed to write teleport-update systemd config files") } - svc := &SystemdService{ + timer := &SystemdService{ ServiceName: filepath.Base(ns.updaterTimerFile), Log: ns.log, } - if err := svc.Sync(ctx); err != nil { + if err := timer.Sync(ctx); err != nil { return trace.Wrap(err, "failed to sync systemd config") } - if err := svc.Enable(ctx, true); err != nil { + if err := timer.Enable(ctx, true); err != nil { return trace.Wrap(err, "failed to enable teleport-update systemd timer") } + if ns.name == "" { + oldTimer := &SystemdService{ + ServiceName: deprecatedTimerName, + Log: ns.log, + } + // If the old teleport-upgrade script is detected, disable it to ensure they do not interfere. + // Note that the schedule is also set to nop by the Teleport agent -- this just prevents restarts. + enabled, err := oldTimer.IsEnabled(ctx) + if err != nil { + return trace.Wrap(err, "failed to determine if deprecated teleport-upgrade systemd timer is enabled") + } + if enabled { + if err := oldTimer.Disable(ctx, true); err != nil { + return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") + } + } + } return nil } @@ -230,7 +252,7 @@ func (ns *Namespace) Teardown(ctx context.Context) error { ServiceName: filepath.Base(ns.updaterTimerFile), Log: ns.log, } - if err := svc.Disable(ctx); err != nil { + if err := svc.Disable(ctx, true); err != nil { return trace.Wrap(err, "failed to disable teleport-update systemd timer") } for _, p := range []string{ @@ -246,9 +268,26 @@ func (ns *Namespace) Teardown(ctx context.Context) error { if err := svc.Sync(ctx); err != nil { return trace.Wrap(err, "failed to sync systemd config") } - if err := os.RemoveAll(ns.versionsDir); err != nil { + if err := os.RemoveAll(namespaceDir(ns.name)); err != nil { return trace.Wrap(err, "failed to remove versions directory") } + if ns.name == "" { + oldTimer := &SystemdService{ + ServiceName: deprecatedTimerName, + Log: ns.log, + } + // If the old upgrader exists, attempt to re-enable it automatically + present, err := oldTimer.IsPresent(ctx) + if err != nil { + return trace.Wrap(err, "failed to determine if deprecated teleport-upgrade systemd timer is present") + } + if present { + ns.log.WarnContext(ctx, "Detected that the deprecated teleport-upgrade script is present on this server. Enabling to ensure continued updates.") + if err := oldTimer.Enable(ctx, true); err != nil { + return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") + } + } + } return nil } diff --git a/lib/service/service.go b/lib/service/service.go index eb98f63552965..1fe54f0e51414 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1230,6 +1230,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { return nil, trace.Wrap(err) } + // Check if the deprecated teleport-upgrader script is being used. upgraderKind := os.Getenv(automaticupgrades.EnvUpgrader) upgraderVersion := automaticupgrades.GetUpgraderVersion(process.GracefulExitContext()) if upgraderVersion == "" { @@ -1243,7 +1244,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { } else if ok { // If this is a teleport-update managed installation, the version // managed by the timer will always match the installed version of teleport. - upgraderKind = "teleport-update" + upgraderKind = types.UpgraderKindTeleportUpdate upgraderVersion = "v" + teleport.Version } @@ -1291,7 +1292,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { if err != nil { return nil, trace.Wrap(err) } - if err := driver.ForceNOP(process.ExitContext()); err != nil { + if err := driver.ForceNop(process.ExitContext()); err != nil { return nil, trace.Wrap(err) } case types.UpgraderKindSystemdUnit: diff --git a/lib/versioncontrol/upgradewindow/upgradewindow.go b/lib/versioncontrol/upgradewindow/upgradewindow.go index 4c1f044a22d87..9f7e969636822 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow.go @@ -45,8 +45,8 @@ const ( // unitScheduleFile is the name of the file to which the unit schedule is exported. unitScheduleFile = "schedule" - // scheduleNOP is the name of the no-op schedule. - scheduleNOP = "nop" + // scheduleNop is the name of the no-op schedule. + scheduleNop = "nop" ) // ExportFunc represents the ExportUpgradeWindows rpc exposed by auth servers. @@ -320,7 +320,7 @@ type Driver interface { // ForceNOP sets the NOP schedule, ensuring that updates do not happen. // This schedule was originally for testing, but now it also ensures that // the teleport-update binary can disable all versions of the teleport-upgrader script. - ForceNOP(ctx context.Context) error + ForceNop(ctx context.Context) error } // NewDriver sets up a new export driver corresponding to the specified upgrader kind. @@ -372,8 +372,8 @@ func (e *kubeDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsRes return trace.Wrap(e.setSchedule(ctx, rsp.KubeControllerSchedule)) } -func (e *kubeDriver) ForceNOP(ctx context.Context) error { - return trace.Wrap(e.setSchedule(ctx, scheduleNOP)) +func (e *kubeDriver) ForceNop(ctx context.Context) error { + return trace.Wrap(e.setSchedule(ctx, scheduleNop)) } func (e *kubeDriver) setSchedule(ctx context.Context, schedule string) error { @@ -430,8 +430,8 @@ func (e *systemdDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindows return trace.Wrap(e.setSchedule(ctx, rsp.SystemdUnitSchedule)) } -func (e *systemdDriver) ForceNOP(ctx context.Context) error { - return trace.Wrap(e.setSchedule(ctx, scheduleNOP)) +func (e *systemdDriver) ForceNop(ctx context.Context) error { + return trace.Wrap(e.setSchedule(ctx, scheduleNop)) } func (e *systemdDriver) setSchedule(ctx context.Context, schedule string) error { diff --git a/lib/versioncontrol/upgradewindow/upgradewindow_test.go b/lib/versioncontrol/upgradewindow/upgradewindow_test.go index cff6aef4207c4..4d1f87efdf157 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow_test.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow_test.go @@ -174,14 +174,14 @@ func TestSystemdUnitDriver(t *testing.T) { require.Equal(t, "fake-schedule-3", string(sb)) - // verify ForceNOP - err = driver.ForceNOP(ctx) + // verify ForceNop + err = driver.ForceNop(ctx) require.NoError(t, err) sb, err = os.ReadFile(schedPath) require.NoError(t, err) - require.Equal(t, scheduleNOP, string(sb)) + require.Equal(t, scheduleNop, string(sb)) // verify that an empty schedule value is treated equivalent to a reset err = driver.Sync(ctx, proto.ExportUpgradeWindowsResponse{}) @@ -219,7 +219,7 @@ func (d *fakeDriver) Sync(ctx context.Context, rsp proto.ExportUpgradeWindowsRes return nil } -func (d *fakeDriver) ForceNOP(ctx context.Context) error { +func (d *fakeDriver) ForceNop(ctx context.Context) error { return trace.NotImplemented("force-nop not used by exporter") } From b6fee9805851cd89d837efe30eb3fc831f50a314 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Mon, 13 Jan 2025 15:06:43 -0500 Subject: [PATCH 07/13] keep schedule on non-suffixed --- lib/autoupdate/agent/telemetry.go | 15 +++++++++++++++ lib/service/service.go | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go index df321f6c791cf..492864b592849 100644 --- a/lib/autoupdate/agent/telemetry.go +++ b/lib/autoupdate/agent/telemetry.go @@ -44,3 +44,18 @@ func IsActive() (bool, error) { systemDir := filepath.Join(teleportOptDir, systemNamespace) return !strings.HasPrefix(absPath, systemDir), nil } + +// IsDefault returns true if the local Teleport binary is both managed by teleport-update +// and the default installation (with teleport.service as the unit file name). +func IsDefault() (bool, error) { + teleportPath, err := os.Readlink("/proc/self/exe") + if err != nil { + return false, trace.Wrap(err, "cannot find Teleport binary") + } + defaultPath := filepath.Join(teleportOptDir, defaultNamespace) + "/" + absPath, err := filepath.Abs(teleportPath) + if err != nil { + return false, trace.Wrap(err, "cannot get absolute path for Teleport binary") + } + return strings.HasPrefix(absPath, defaultPath), nil +} diff --git a/lib/service/service.go b/lib/service/service.go index 1fe54f0e51414..889506b2eb152 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1288,6 +1288,15 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { switch upgraderKind { case types.UpgraderKindTeleportUpdate: + isDefault, err := autoupdate.IsDefault() + if err != nil { + return nil, trace.Wrap(err) + } + if !isDefault { + // Only write the nop schedule for the default updater. + // Suffixed installations of Teleport can coexist with the old upgrader system. + break + } driver, err := uw.NewSystemdUnitDriver(uw.SystemdUnitDriverConfig{}) if err != nil { return nil, trace.Wrap(err) From 1eb891a31c0d11815f3324f452d408834fdf52d1 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Wed, 15 Jan 2025 20:01:52 -0500 Subject: [PATCH 08/13] Update maintenance.go --- api/types/maintenance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/types/maintenance.go b/api/types/maintenance.go index 318cabe7df73d..5d98a3ec311cb 100644 --- a/api/types/maintenance.go +++ b/api/types/maintenance.go @@ -36,7 +36,7 @@ const ( // UpgraderKindTeleportUpdate is a short name used to identify the teleport-update // external upgrader variant. - UpgraderKindTeleportUpdate = "updater" + UpgraderKindTeleportUpdate = "binary" ) var validWeekdays = [7]time.Weekday{ From 1f32dd00c4f487ca7f98aa706fdcab145cdb70c0 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Wed, 15 Jan 2025 20:12:19 -0500 Subject: [PATCH 09/13] Update lib/autoupdate/agent/setup.go --- lib/autoupdate/agent/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/autoupdate/agent/setup.go b/lib/autoupdate/agent/setup.go index a878c73e860dc..e212cd2f27725 100644 --- a/lib/autoupdate/agent/setup.go +++ b/lib/autoupdate/agent/setup.go @@ -282,7 +282,7 @@ func (ns *Namespace) Teardown(ctx context.Context) error { return trace.Wrap(err, "failed to determine if deprecated teleport-upgrade systemd timer is present") } if present { - ns.log.WarnContext(ctx, "Detected that the deprecated teleport-upgrade script is present on this server. Enabling to ensure continued updates.") + ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been re-enabled to ensure continued updates. To disable automatic updates entirely, please remove the teleport-ent-updater package.") if err := oldTimer.Enable(ctx, true); err != nil { return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") } From 84555ff8f2c9e578c59c60532b781d80bc94341b Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Wed, 15 Jan 2025 22:17:01 -0500 Subject: [PATCH 10/13] update warnings --- lib/autoupdate/agent/setup.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/autoupdate/agent/setup.go b/lib/autoupdate/agent/setup.go index e212cd2f27725..fbabed91e5bd3 100644 --- a/lib/autoupdate/agent/setup.go +++ b/lib/autoupdate/agent/setup.go @@ -241,6 +241,7 @@ func (ns *Namespace) Setup(ctx context.Context) error { if err := oldTimer.Disable(ctx, true); err != nil { return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") } + ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been disabled to prevent conflicts. Please remove the teleport-ent-updater package after verifying that teleport-update is working.") } } return nil @@ -282,10 +283,10 @@ func (ns *Namespace) Teardown(ctx context.Context) error { return trace.Wrap(err, "failed to determine if deprecated teleport-upgrade systemd timer is present") } if present { - ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been re-enabled to ensure continued updates. To disable automatic updates entirely, please remove the teleport-ent-updater package.") if err := oldTimer.Enable(ctx, true); err != nil { return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") } + ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been re-enabled to ensure continued updates. To disable automatic updates entirely, please remove the teleport-ent-updater package.") } } return nil From 6686d649d2105e46755ae9c6963de4e0aca3d9c1 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Tue, 21 Jan 2025 15:25:14 -0500 Subject: [PATCH 11/13] feedback pt 1 --- lib/autoupdate/agent/process.go | 7 +- lib/autoupdate/agent/setup.go | 10 +-- lib/autoupdate/agent/telemetry.go | 47 +++++++++---- lib/autoupdate/agent/telemetry_test.go | 91 ++++++++++++++++++++++++++ lib/service/service.go | 4 +- 5 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 lib/autoupdate/agent/telemetry_test.go diff --git a/lib/autoupdate/agent/process.go b/lib/autoupdate/agent/process.go index 51a2f62cd9bff..a24b4605e1924 100644 --- a/lib/autoupdate/agent/process.go +++ b/lib/autoupdate/agent/process.go @@ -322,13 +322,10 @@ func (s SystemdService) IsPresent(ctx context.Context) (bool, error) { return false, trace.Wrap(err) } code := s.systemctl(ctx, slog.LevelDebug, "list-unit-files", "--quiet", s.ServiceName) - switch { - case code < 0: + if code < 0 { return false, trace.Errorf("unable to determine if systemd service %s is present", s.ServiceName) - case code == 0: - return true, nil } - return false, nil + return code == 0, nil } // checkSystem returns an error if the system is not compatible with this process manager. diff --git a/lib/autoupdate/agent/setup.go b/lib/autoupdate/agent/setup.go index fbabed91e5bd3..163b8ea3171e2 100644 --- a/lib/autoupdate/agent/setup.go +++ b/lib/autoupdate/agent/setup.go @@ -239,9 +239,10 @@ func (ns *Namespace) Setup(ctx context.Context) error { } if enabled { if err := oldTimer.Disable(ctx, true); err != nil { - return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") + ns.log.ErrorContext(ctx, "The deprecated teleport-ent-updater package is installed on this server, and it cannot be disabled due to an error. You must remove the teleport-ent-updater package after verifying that teleport-update is working.", errorKey, err) + } else { + ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been disabled to prevent conflicts. Please remove the teleport-ent-updater package after verifying that teleport-update is working.") } - ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been disabled to prevent conflicts. Please remove the teleport-ent-updater package after verifying that teleport-update is working.") } } return nil @@ -284,9 +285,10 @@ func (ns *Namespace) Teardown(ctx context.Context) error { } if present { if err := oldTimer.Enable(ctx, true); err != nil { - return trace.Wrap(err, "failed to disable deprecated teleport-upgrade systemd timer") + ns.log.ErrorContext(ctx, "The deprecated teleport-ent-updater package is installed on this server, and it cannot be re-enabled due to an error. Please fix the teleport-ent-updater package if you intend to use the deprecated updater.", errorKey, err) + } else { + ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been re-enabled to ensure continued updates. To disable automatic updates entirely, please remove the teleport-ent-updater package.") } - ns.log.WarnContext(ctx, "The deprecated teleport-ent-updater package is installed on this server. This package has been re-enabled to ensure continued updates. To disable automatic updates entirely, please remove the teleport-ent-updater package.") } } return nil diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go index 492864b592849..529460beda78a 100644 --- a/lib/autoupdate/agent/telemetry.go +++ b/lib/autoupdate/agent/telemetry.go @@ -26,36 +26,55 @@ import ( "github.com/gravitational/trace" ) -// IsActive returns true if the local Teleport binary is managed by teleport-update. +// IsManagedByUpdater returns true if the local Teleport binary is managed by teleport-update. // Note that true may be returned even if auto-updates is disabled or the version is pinned. -func IsActive() (bool, error) { +// The binary is considered managed if it lives under /opt/teleport, but not within the package +// path at /opt/teleport/system. +func IsManagedByUpdater() (bool, error) { teleportPath, err := os.Readlink("/proc/self/exe") if err != nil { return false, trace.Wrap(err, "cannot find Teleport binary") } - updaterBasePath := filepath.Clean(teleportOptDir) + "/" - absPath, err := filepath.Abs(teleportPath) + // Check if current binary is under the updater-managed path. + managed, err := hasParentDir(teleportPath, teleportOptDir) if err != nil { - return false, trace.Wrap(err, "cannot get absolute path for Teleport binary") + return false, trace.Wrap(err) } - if !strings.HasPrefix(absPath, updaterBasePath) { + if !managed { return false, nil } - systemDir := filepath.Join(teleportOptDir, systemNamespace) - return !strings.HasPrefix(absPath, systemDir), nil + // Return false if the binary is under the updater-managed path, but in the system prefix reserved for the package. + system, err := hasParentDir(teleportPath, filepath.Join(teleportOptDir, systemNamespace)) + return !system, err } -// IsDefault returns true if the local Teleport binary is both managed by teleport-update +// IsManagedAndDefault returns true if the local Teleport binary is both managed by teleport-update // and the default installation (with teleport.service as the unit file name). -func IsDefault() (bool, error) { +// The binary is considered managed and default if it lives within /opt/teleport/default. +func IsManagedAndDefault() (bool, error) { teleportPath, err := os.Readlink("/proc/self/exe") if err != nil { return false, trace.Wrap(err, "cannot find Teleport binary") } - defaultPath := filepath.Join(teleportOptDir, defaultNamespace) + "/" - absPath, err := filepath.Abs(teleportPath) + return hasParentDir(teleportPath, filepath.Join(teleportOptDir, defaultNamespace)) +} + +// hasParentDir returns true if dir is any parent directory of parent. +// hasParentDir does not resolve symlinks, and requires that files be represented the same way in dir and parent. +func hasParentDir(dir, parent string) (bool, error) { + // Note that os.Stat + os.SameFile would be more reliable, + // but does not work well for arbitrarily nested subdirectories. + absDir, err := filepath.Abs(dir) if err != nil { - return false, trace.Wrap(err, "cannot get absolute path for Teleport binary") + return false, trace.Wrap(err, "cannot get absolute path for directory %s", dir) + } + absParent, err := filepath.Abs(parent) + if err != nil { + return false, trace.Wrap(err, "cannot get absolute path for parent directory %s", dir) + } + sep := string(filepath.Separator) + if !strings.HasSuffix(absParent, sep) { + absParent += sep } - return strings.HasPrefix(absPath, defaultPath), nil + return strings.HasPrefix(absDir, absParent), nil } diff --git a/lib/autoupdate/agent/telemetry_test.go b/lib/autoupdate/agent/telemetry_test.go new file mode 100644 index 0000000000000..e1d88d22df5ee --- /dev/null +++ b/lib/autoupdate/agent/telemetry_test.go @@ -0,0 +1,91 @@ +package agent + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHasParentDir(t *testing.T) { + tests := []struct { + name string + path string + parent string + wantResult bool + }{ + { + name: "Has valid parent directory", + path: "/opt/teleport/dir/test", + parent: "/opt/teleport", + wantResult: true, + }, + { + name: "Has valid parent directory with slash", + path: "/opt/teleport/dir/test", + parent: "/opt/teleport/", + wantResult: true, + }, + { + name: "Parent directory is root", + path: "/opt/teleport/dir", + parent: "/", + wantResult: true, + }, + { + name: "Parent is the same as the path", + path: "/opt/teleport/dir", + parent: "/opt/teleport/dir", + wantResult: false, + }, + { + name: "Parent the same as the path but without slash", + path: "/opt/teleport/dir/", + parent: "/opt/teleport/dir", + wantResult: false, + }, + { + name: "Parent the same as the path but with slash", + path: "/opt/teleport/dir", + parent: "/opt/teleport/dir/", + wantResult: false, + }, + { + name: "Parent is substring of the path", + path: "/opt/teleport/dir-place", + parent: "/opt/teleport/dir", + wantResult: false, + }, + { + name: "Parent is in path", + path: "/opt/teleport", + parent: "/opt/teleport/dir", + wantResult: false, + }, + { + name: "Empty parent", + path: "/opt/teleport/dir", + parent: "", + wantResult: false, + }, + { + name: "Empty path", + path: "", + parent: "/opt/teleport", + wantResult: false, + }, + { + name: "Both empty", + path: "", + parent: "", + wantResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := hasParentDir(tt.path, tt.parent) + require.NoError(t, err) + require.Equal(t, tt.wantResult, result) + }) + } +} diff --git a/lib/service/service.go b/lib/service/service.go index 889506b2eb152..4a43d2910b6a8 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1238,7 +1238,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { } // If the installation is managed by teleport-update, it supersedes the teleport-upgrader script. - ok, err := autoupdate.IsActive() + ok, err := autoupdate.IsManagedByUpdater() if err != nil { process.logger.WarnContext(process.ExitContext(), "Failed to determine if auto-updates are enabled.", "error", err) } else if ok { @@ -1288,7 +1288,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { switch upgraderKind { case types.UpgraderKindTeleportUpdate: - isDefault, err := autoupdate.IsDefault() + isDefault, err := autoupdate.IsManagedAndDefault() if err != nil { return nil, trace.Wrap(err) } From 1c47493d6cf6fbc458f42675dfbe8b085969a015 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Tue, 21 Jan 2025 15:54:47 -0500 Subject: [PATCH 12/13] feedback pt 2 --- lib/service/service.go | 103 +++++++++++------- .../upgradewindow/upgradewindow.go | 7 +- .../upgradewindow/upgradewindow_test.go | 36 ++++-- 3 files changed, 94 insertions(+), 52 deletions(-) diff --git a/lib/service/service.go b/lib/service/service.go index 4a43d2910b6a8..cb58fc5031976 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1230,30 +1230,7 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { return nil, trace.Wrap(err) } - // Check if the deprecated teleport-upgrader script is being used. - upgraderKind := os.Getenv(automaticupgrades.EnvUpgrader) - upgraderVersion := automaticupgrades.GetUpgraderVersion(process.GracefulExitContext()) - if upgraderVersion == "" { - upgraderKind = "" - } - - // If the installation is managed by teleport-update, it supersedes the teleport-upgrader script. - ok, err := autoupdate.IsManagedByUpdater() - if err != nil { - process.logger.WarnContext(process.ExitContext(), "Failed to determine if auto-updates are enabled.", "error", err) - } else if ok { - // If this is a teleport-update managed installation, the version - // managed by the timer will always match the installed version of teleport. - upgraderKind = types.UpgraderKindTeleportUpdate - upgraderVersion = "v" + teleport.Version - } - - // Instances deployed using the AWS OIDC integration are automatically updated - // by the proxy. The instance heartbeat should properly reflect that. - externalUpgrader := upgraderKind - if externalUpgrader == "" && os.Getenv(types.InstallMethodAWSOIDCDeployServiceEnvVar) == "true" { - externalUpgrader = types.OriginIntegrationAWSOIDC - } + upgraderKind, externalUpgrader, upgraderVersion := process.detectUpgrader() // note: we must create the inventory handle *after* registerExpectedServices because that function determines // the list of services (instance roles) to be included in the heartbeat. @@ -1332,28 +1309,13 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { process.logger.InfoContext(process.ExitContext(), "Exported autoupdates endpoint.", "addr", resolverAddr.String()) return nil }) - fallthrough - default: - driver, err := uw.NewDriver(upgraderKind) - if err != nil { + if err := process.configureUpgraderExporter(upgraderKind); err != nil { return nil, trace.Wrap(err) } - - exporter, err := uw.NewExporter(uw.ExporterConfig[inventory.DownstreamSender]{ - Driver: driver, - ExportFunc: process.exportUpgradeWindows, - AuthConnectivitySentinel: process.inventoryHandle.Sender(), - }) - if err != nil { + default: + if err := process.configureUpgraderExporter(upgraderKind); err != nil { return nil, trace.Wrap(err) } - - process.RegisterCriticalFunc("upgradeewindow.export", exporter.Run) - process.OnExit("upgradewindow.export.stop", func(_ interface{}) { - exporter.Close() - }) - - process.logger.InfoContext(process.ExitContext(), "Configured upgrade window exporter for external upgrader.", "kind", upgraderKind) } } @@ -1562,6 +1524,63 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) { return process, nil } +// detectUpgrader returns metadata about auto-upgraders that may be active. +// Note that kind and externalName are usually the same. +// However, some unregistered upgraders like the AWS ODIC upgrader are not valid kinds. +// For these upgraders, kind is empty and externalName is set to a non-kind value. +func (process *TeleportProcess) detectUpgrader() (kind, externalName, version string) { + // Check if the deprecated teleport-upgrader script is being used. + kind = os.Getenv(automaticupgrades.EnvUpgrader) + version = automaticupgrades.GetUpgraderVersion(process.GracefulExitContext()) + if version == "" { + kind = "" + } + + // If the installation is managed by teleport-update, it supersedes the teleport-upgrader script. + ok, err := autoupdate.IsManagedByUpdater() + if err != nil { + process.logger.WarnContext(process.ExitContext(), "Failed to determine if auto-updates are enabled.", "error", err) + } else if ok { + // If this is a teleport-update managed installation, the version + // managed by the timer will always match the installed version of teleport. + kind = types.UpgraderKindTeleportUpdate + version = "v" + teleport.Version + } + + // Instances deployed using the AWS OIDC integration are automatically updated + // by the proxy. The instance heartbeat should properly reflect that. + externalName = kind + if externalName == "" && os.Getenv(types.InstallMethodAWSOIDCDeployServiceEnvVar) == "true" { + externalName = types.OriginIntegrationAWSOIDC + } + return kind, externalName, version +} + +// configureUpgraderExporter configures the window exporter for upgraders that export windows. +func (process *TeleportProcess) configureUpgraderExporter(kind string) error { + driver, err := uw.NewDriver(kind) + if err != nil { + return trace.Wrap(err) + } + + exporter, err := uw.NewExporter(uw.ExporterConfig[inventory.DownstreamSender]{ + Driver: driver, + ExportFunc: process.exportUpgradeWindows, + AuthConnectivitySentinel: process.inventoryHandle.Sender(), + }) + if err != nil { + return trace.Wrap(err) + } + + process.RegisterCriticalFunc("upgradeewindow.export", exporter.Run) + process.OnExit("upgradewindow.export.stop", func(_ interface{}) { + exporter.Close() + }) + + process.logger.InfoContext(process.ExitContext(), "Configured upgrade window exporter for external upgrader.", "kind", kind) + return nil +} + // enterpriseServicesEnabled will return true if any enterprise services are enabled. func (process *TeleportProcess) enterpriseServicesEnabled() bool { return modules.GetModules().BuildType() == modules.BuildEnterprise && diff --git a/lib/versioncontrol/upgradewindow/upgradewindow.go b/lib/versioncontrol/upgradewindow/upgradewindow.go index 9f7e969636822..14606a7cc8f93 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow.go @@ -317,9 +317,10 @@ type Driver interface { // that the control plane has been upgraded s.t. this agent is no longer compatible. Reset(ctx context.Context) error - // ForceNOP sets the NOP schedule, ensuring that updates do not happen. - // This schedule was originally for testing, but now it also ensures that - // the teleport-update binary can disable all versions of the teleport-upgrader script. + // ForceNop sets the NOP schedule, ensuring that updates do not happen. + // This schedule was originally only used for testing, but now it is also used by the + // teleport-update binary to protect against package updates that could interfere with + // the new update system. ForceNop(ctx context.Context) error } diff --git a/lib/versioncontrol/upgradewindow/upgradewindow_test.go b/lib/versioncontrol/upgradewindow/upgradewindow_test.go index 4d1f87efdf157..c5c2236673ea7 100644 --- a/lib/versioncontrol/upgradewindow/upgradewindow_test.go +++ b/lib/versioncontrol/upgradewindow/upgradewindow_test.go @@ -174,22 +174,44 @@ func TestSystemdUnitDriver(t *testing.T) { require.Equal(t, "fake-schedule-3", string(sb)) - // verify ForceNop - err = driver.ForceNop(ctx) + // verify that an empty schedule value is treated equivalent to a reset + err = driver.Sync(ctx, proto.ExportUpgradeWindowsResponse{}) require.NoError(t, err) sb, err = os.ReadFile(schedPath) require.NoError(t, err) + require.Equal(t, "", string(sb)) +} - require.Equal(t, scheduleNop, string(sb)) +// TestSystemdUnitDriverNop verifies the nop schedule behavior of the systemd unit export driver. +func TestSystemdUnitDriverNop(t *testing.T) { + t.Parallel() - // verify that an empty schedule value is treated equivalent to a reset - err = driver.Sync(ctx, proto.ExportUpgradeWindowsResponse{}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // use a sub-directory of a temp dir in order to verify that + // driver creates dir when needed. + dir := filepath.Join(t.TempDir(), "config") + + driver, err := NewSystemdUnitDriver(SystemdUnitDriverConfig{ + ConfigDir: dir, + }) require.NoError(t, err) - sb, err = os.ReadFile(schedPath) + err = driver.Sync(ctx, proto.ExportUpgradeWindowsResponse{ + SystemdUnitSchedule: "fake-schedule", + }) require.NoError(t, err) - require.Equal(t, "", string(sb)) + + err = driver.ForceNop(ctx) + require.NoError(t, err) + + schedPath := filepath.Join(dir, "schedule") + sb, err := os.ReadFile(schedPath) + require.NoError(t, err) + + require.Equal(t, scheduleNop, string(sb)) } // fakeDriver is used to inject custom behavior into a dummy Driver instance. From 22798456a87463d7379d8be9cb3ce2c09d274a40 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Tue, 21 Jan 2025 16:06:07 -0500 Subject: [PATCH 13/13] headers --- lib/autoupdate/agent/telemetry.go | 2 +- lib/autoupdate/agent/telemetry_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/autoupdate/agent/telemetry.go b/lib/autoupdate/agent/telemetry.go index 529460beda78a..a1abeb1b3d768 100644 --- a/lib/autoupdate/agent/telemetry.go +++ b/lib/autoupdate/agent/telemetry.go @@ -1,6 +1,6 @@ /* * Teleport - * Copyright (C) 2024 Gravitational, Inc. + * Copyright (C) 2025 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 diff --git a/lib/autoupdate/agent/telemetry_test.go b/lib/autoupdate/agent/telemetry_test.go index e1d88d22df5ee..8332657785c04 100644 --- a/lib/autoupdate/agent/telemetry_test.go +++ b/lib/autoupdate/agent/telemetry_test.go @@ -1,3 +1,21 @@ +/* + * Teleport + * Copyright (C) 2025 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 agent import (