diff --git a/lib/service/service.go b/lib/service/service.go index b53d24dd61889..fab715c5db0cb 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -1282,7 +1282,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 { @@ -1310,9 +1319,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 7f90a9d70d41c..6e9423b7c2487 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()