From 0c9a79b24869d57ad5df6601c36fbff78350a21d Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Thu, 26 Jan 2023 19:52:03 -0300 Subject: [PATCH] Adjust Nudge configuration to match the specification (#9519) Related to #9013 this adjusts the Nudge configuration to match the spec. --- .../Using-Fleet/configuration-files/README.md | 4 +- server/fleet/nudge.go | 97 +++++++++++++++++++ server/fleet/orbit.go | 2 +- server/service/integration_enterprise_test.go | 49 +++------- server/service/orbit.go | 27 ++---- 5 files changed, 122 insertions(+), 57 deletions(-) create mode 100644 server/fleet/nudge.go diff --git a/docs/Using-Fleet/configuration-files/README.md b/docs/Using-Fleet/configuration-files/README.md index 7d1e655763..7e840bb74e 100644 --- a/docs/Using-Fleet/configuration-files/README.md +++ b/docs/Using-Fleet/configuration-files/README.md @@ -1262,7 +1262,9 @@ Requires `mdm.macos_updates.deadline` to be set. ##### mdm.macos_updates.deadline -A deadline in the form `YYYY-MM-DD`. Hosts that belong to no team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. +A deadline in the form `YYYY-MM-DD`. The exact deadline time is at 04:00:00 (UTC-8). + +Hosts that belong to no team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. Requires `mdm.macos_updates.minimum_version` to be set. diff --git a/server/fleet/nudge.go b/server/fleet/nudge.go new file mode 100644 index 0000000000..fa86c831a1 --- /dev/null +++ b/server/fleet/nudge.go @@ -0,0 +1,97 @@ +package fleet + +import ( + "time" +) + +// NudgeConfig represents a subset of the [full Nudge configuration][0] that we +// want to override. +// +// [0]: https://github.com/macadmins/nudge/wiki +type NudgeConfig struct { + OSVersionRequirements []nudgeOSVersionRequirements `json:"osVersionRequirements"` + UserInterface nudgeUserInterface `json:"userInterface"` + UserExperience nudgeUserExperience `json:"userExperience"` + UpdateElements []nudgeUpdateElements `json:"updateElements"` +} + +type nudgeAboutUpdateURLs struct { + Language string `json:"_language"` + AboutUpdateURL string `json:"aboutUpdateURL"` +} + +type nudgeOSVersionRequirements struct { + RequiredInstallationDate time.Time `json:"requiredInstallationDate"` + RequiredMinimumOSVersion string `json:"requiredMinimumOSVersion"` + AboutUpdateURLs []nudgeAboutUpdateURLs `json:"aboutUpdateURLs"` +} + +type nudgeUserInterface struct { + SimpleMode bool `json:"simpleMode"` + ShowDeferralCount bool `json:"showDeferralCount"` +} + +type nudgeUserExperience struct { + InitialRefreshCycle int `json:"initialRefreshCycle"` + ApproachingRefreshCycle int `json:"approachingRefreshCycle"` + ImminentRefreshCycle int `json:"imminentRefreshCycle"` + ElapsedRefreshCycle int `json:"elapsedRefreshCycle"` +} + +type nudgeUpdateElements struct { + Language string `json:"_language"` + ActionButtonText string `json:"actionButtonText"` + MainHeader string `json:"mainHeader"` +} + +func NewNudgeConfig(macOSUpdates MacOSUpdates) (*NudgeConfig, error) { + deadline, err := time.Parse("2006-01-02", macOSUpdates.Deadline) + if err != nil { + return nil, err + } + + // Per the spec, the exact deadline time is arbitrarily chosen to be + // 04:00:00 (UTC-8) until we allow users to customize it. + // + // See https://github.com/fleetdm/fleet/issues/9013 for more details. + localizedDeadline := time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 4, 0, 0, 0, time.UTC) + + return &NudgeConfig{ + OSVersionRequirements: []nudgeOSVersionRequirements{{ + RequiredInstallationDate: localizedDeadline, + RequiredMinimumOSVersion: macOSUpdates.MinimumVersion, + AboutUpdateURLs: []nudgeAboutUpdateURLs{{ + Language: "en", + AboutUpdateURL: "https://fleetdm.com/docs/using-fleet/mobile-device-management#macos-updates", + }}, + }}, + UserInterface: nudgeUserInterface{ + SimpleMode: true, + ShowDeferralCount: false, + }, + UserExperience: nudgeUserExperience{ + /* Initially, we show Nudge once every 24 hours */ + InitialRefreshCycle: 86400, + /* + * Related to approachingWindowTime (72 hours before deadline by default) + * we still want to show the window once every 24 hours. + */ + ApproachingRefreshCycle: 86400, + /* + * Related to imminentWindowTime (24 hours before deadline by default) + * we want to show the window once every 2 hours. + */ + ImminentRefreshCycle: 7200, + /* + * Related to elapsedWindowTime (once the deadline is past) + * we want to show the window once every hour. + */ + ElapsedRefreshCycle: 3600, + }, + UpdateElements: []nudgeUpdateElements{{ + Language: "en", + ActionButtonText: "Update", + MainHeader: "Your device requires an update", + }}, + }, nil +} diff --git a/server/fleet/orbit.go b/server/fleet/orbit.go index 53428ca849..8398c6b83a 100644 --- a/server/fleet/orbit.go +++ b/server/fleet/orbit.go @@ -12,6 +12,6 @@ type OrbitConfigNotifications struct { type OrbitConfig struct { Flags json.RawMessage `json:"command_line_startup_flags,omitempty"` Extensions json.RawMessage `json:"extensions,omitempty"` - NudgeConfig json.RawMessage `json:"nudge_config,omitempty"` + NudgeConfig *NudgeConfig `json:"nudge_config,omitempty"` Notifications OrbitConfigNotifications `json:"notifications,omitempty"` } diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 0e6ba2848b..d2a55c5a7d 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -2141,18 +2141,10 @@ func (s *integrationEnterpriseTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp) - require.JSONEq( - t, - `{ - "osVersionRequirements": [ - { - "requiredInstallationDate": "2022-01-04", - "requiredMinimumOSVersion": "12.1.3" - } - ] - }`, - string(resp.NudgeConfig), - ) + wantCfg, err := fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: "2022-01-04", MinimumVersion: "12.1.3"}) + require.NoError(t, err) + require.Equal(t, wantCfg, resp.NudgeConfig) + require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "2022-01-04 04:00:00 +0000 UTC") // create a team with an empty macos_updates config team, err := s.ds.NewTeam(context.Background(), &fleet.Team{ @@ -2170,6 +2162,7 @@ func (s *integrationEnterpriseTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp) require.Empty(t, resp.NudgeConfig) + require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "2022-01-04 04:00:00 +0000 UTC") // modify the team config, add macos_updates config var tmResp teamResponse @@ -2184,35 +2177,19 @@ func (s *integrationEnterpriseTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp) - require.JSONEq( - t, - `{ - "osVersionRequirements": [ - { - "requiredInstallationDate": "1992-01-01", - "requiredMinimumOSVersion": "13.1.1" - } - ] - }`, - string(resp.NudgeConfig), - ) + wantCfg, err = fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: "1992-01-01", MinimumVersion: "13.1.1"}) + require.NoError(t, err) + require.Equal(t, wantCfg, resp.NudgeConfig) + require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "1992-01-01 04:00:00 +0000 UTC") // create a new host, still receives the global config h2 := createOrbitEnrolledHost(t, "darwin", "h2", s.ds) resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h2.OrbitNodeKey)), http.StatusOK, &resp) - require.JSONEq( - t, - `{ - "osVersionRequirements": [ - { - "requiredInstallationDate": "2022-01-04", - "requiredMinimumOSVersion": "12.1.3" - } - ] - }`, - string(resp.NudgeConfig), - ) + wantCfg, err = fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: "2022-01-04", MinimumVersion: "12.1.3"}) + require.NoError(t, err) + require.Equal(t, wantCfg, resp.NudgeConfig) + require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "2022-01-04 04:00:00 +0000 UTC") } // allEqual compares all fields of a struct. diff --git a/server/service/orbit.go b/server/service/orbit.go index 785c30d09c..77d1213123 100644 --- a/server/service/orbit.go +++ b/server/service/orbit.go @@ -1,12 +1,10 @@ package service import ( - "bytes" "context" "encoding/json" "fmt" "net/http" - "text/template" "github.com/fleetdm/fleet/v4/server" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" @@ -167,11 +165,12 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro return fleet.OrbitConfig{Notifications: notifs}, err } - var nudgeConfig bytes.Buffer + var nudgeConfig *fleet.NudgeConfig if mdmConfig != nil && mdmConfig.MacOSUpdates.Deadline != "" && mdmConfig.MacOSUpdates.MinimumVersion != "" { - if err := nudgeConfigTemplate.Execute(&nudgeConfig, mdmConfig.MacOSUpdates); err != nil { + nudgeConfig, err = fleet.NewNudgeConfig(mdmConfig.MacOSUpdates) + if err != nil { return fleet.OrbitConfig{Notifications: notifs}, err } } @@ -180,7 +179,7 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro Flags: opts.CommandLineStartUpFlags, Extensions: opts.Extensions, Notifications: notifs, - NudgeConfig: nudgeConfig.Bytes(), + NudgeConfig: nudgeConfig, }, nil } @@ -196,10 +195,11 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro } } - var nudgeConfig bytes.Buffer + var nudgeConfig *fleet.NudgeConfig if config.MDM.MacOSUpdates.Deadline != "" && config.MDM.MacOSUpdates.MinimumVersion != "" { - if err := nudgeConfigTemplate.Execute(&nudgeConfig, config.MDM.MacOSUpdates); err != nil { + nudgeConfig, err = fleet.NewNudgeConfig(config.MDM.MacOSUpdates) + if err != nil { return fleet.OrbitConfig{Notifications: notifs}, err } } @@ -208,21 +208,10 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro Flags: opts.CommandLineStartUpFlags, Extensions: opts.Extensions, Notifications: notifs, - NudgeConfig: nudgeConfig.Bytes(), + NudgeConfig: nudgeConfig, }, nil } -var nudgeConfigTemplate = template.Must(template.New("").Option("missingkey=error").Parse(` -{ - "osVersionRequirements": [ - { - "requiredInstallationDate": "{{ .Deadline }}", - "requiredMinimumOSVersion": "{{ .MinimumVersion }}" - } - ] -} -`)) - ///////////////////////////////////////////////////////////////////////////////// // Ping orbit endpoint /////////////////////////////////////////////////////////////////////////////////