chore: merge main

This commit is contained in:
Jahziel Villasana-Espinoza
2024-07-24 16:50:43 -04:00
94 changed files with 2395 additions and 973 deletions
+30 -11
View File
@@ -323,15 +323,16 @@ func processFailingHostExistingCalendarEvent(
// Try to acquire the lock. Lock is needed to ensure calendar callback is not processed for this event at the same time.
eventUUID := calendarEvent.UUID
lockValue := uuid.New().String()
lockAcquired, err := distributedLock.AcquireLock(ctx, calendar.LockKeyPrefix+eventUUID, lockValue, 0)
lockAcquired, err := distributedLock.AcquireLock(ctx, calendar.LockKeyPrefix+eventUUID, lockValue, calendar.DistributedLockExpireMs)
if err != nil {
return fmt.Errorf("acquire calendar lock: %w", err)
}
lockReserved := false
if !lockAcquired {
// Lock was not acquired. We reserve the lock and try to acquire it until we do.
var timeoutMs uint64 = 2 * 60 * 1000
lockAcquired, err = distributedLock.AcquireLock(ctx, calendar.ReservedLockKeyPrefix+eventUUID, lockValue, timeoutMs)
lockAcquired, err = distributedLock.AcquireLock(ctx, calendar.ReservedLockKeyPrefix+eventUUID, lockValue,
calendar.ReserveLockExpireMs)
if err != nil {
return fmt.Errorf("reserve calendar lock: %w", err)
}
@@ -344,12 +345,13 @@ func processFailingHostExistingCalendarEvent(
go func() {
for {
// Keep trying to get the lock.
lockAcquired, err = distributedLock.AcquireLock(ctx, calendar.LockKeyPrefix+eventUUID, lockValue, 0)
lockAcquired, err = distributedLock.AcquireLock(ctx, calendar.LockKeyPrefix+eventUUID, lockValue,
calendar.DistributedLockExpireMs)
if err != nil || lockAcquired {
done <- struct{}{}
return
}
time.Sleep(100 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
}
}()
select {
@@ -358,7 +360,7 @@ func processFailingHostExistingCalendarEvent(
if err != nil {
return fmt.Errorf("try to acquire calendar lock: %w", err)
}
case <-time.After(time.Duration(timeoutMs) * time.Millisecond):
case <-time.After(time.Duration(calendar.ReserveLockExpireMs) * time.Millisecond):
// We couldn't acquire the lock in time.
return errors.New("could not acquire calendar lock in time")
}
@@ -372,7 +374,7 @@ func processFailingHostExistingCalendarEvent(
}
if !ok {
// If the lock was not released, it will expire on its own.
level.Warn(logger).Log("msg", "Failed to release calendar reserve lock")
level.Error(logger).Log("msg", "Failed to release calendar reserve lock", "event uuid", eventUUID, "lockValue", lockValue)
}
}
ok, err := distributedLock.ReleaseLock(ctx, calendar.LockKeyPrefix+eventUUID, lockValue)
@@ -380,8 +382,8 @@ func processFailingHostExistingCalendarEvent(
level.Error(logger).Log("msg", "Failed to release calendar lock", "err", err)
}
if !ok {
// If the lock was not released, it will expire on its own.
level.Warn(logger).Log("msg", "Failed to release calendar lock")
// If the lock was not released, it will expire on its own. However, we should adjust expiration time or something else to make sure we don't get here.
level.Error(logger).Log("msg", "Failed to release calendar lock", "event uuid", eventUUID, "lockValue", lockValue)
}
}()
@@ -390,11 +392,23 @@ func processFailingHostExistingCalendarEvent(
now := time.Now()
if calendarConfig.AlwaysReloadEvent() || shouldReloadCalendarEvent(now, calendarEvent, hostCalendarEvent) {
var err error
// Refetch the event since it may have updated since we got the lock.
// We need the latest event data (ETag) to make sure that we get correct data from the calendar service.
calendarEvent, err = ds.GetCalendarEvent(ctx, calendarEvent.Email)
if err != nil {
if fleet.IsNotFound(err) {
// Event was deleted while we were processing it. It will be recreated if needed on the next cron run
return nil
}
return fmt.Errorf("get calendar event from db: %w", err)
}
// We could check the updated_at timestamp and avoid updating the event if it was updated recently.
updatedEvent, _, err = userCalendar.GetAndUpdateEvent(
calendarEvent, func(conflict bool) (string, bool, error) {
return calendar.GenerateCalendarEventBody(ctx, ds, orgName, host, policyIDtoPolicy, conflict, logger), true, nil
},
fleet.CalendarGetAndUpdateEventOpts{UpdateTimezone: true},
)
if err != nil {
return fmt.Errorf("get event calendar on db: %w", err)
@@ -417,6 +431,7 @@ func processFailingHostExistingCalendarEvent(
}
// Remove event from the queue so that we don't process this event again.
// If we just modified the event in the calendar, calendar will send a callback, and we don't need to process that callback.
err = distributedLock.RemoveFromSet(ctx, calendar.QueueKey, eventUUID)
if err != nil {
return fmt.Errorf("remove calendar event from queue: %w", err)
@@ -528,7 +543,7 @@ func attemptCreatingEventOnUserCalendar(
calendarEvent, err := userCalendar.CreateEvent(
preferredDate, func(conflict bool) (string, bool, error) {
return calendar.GenerateCalendarEventBody(ctx, ds, orgName, host, policyIDtoPolicy, conflict, logger), true, nil
},
}, fleet.CalendarCreateEventOpts{},
)
var dee fleet.DayEndedError
switch {
@@ -817,6 +832,10 @@ func deleteCalendarEvent(
return fmt.Errorf("delete calendar event: %w", err)
}
}
// Stop watching for calendar changes
if err := userCalendar.StopEventChannel(calendarEvent); err != nil {
return fmt.Errorf("stop event channel: %w", err)
}
}
if err := ds.DeleteCalendarEvent(ctx, calendarEvent.ID); err != nil {
return fmt.Errorf("delete db calendar event: %w", err)
@@ -559,7 +559,7 @@ func TestCachedTeamMDMConfig(t *testing.T) {
testMDMConfig := fleet.TeamMDM{
EnableDiskEncryption: true,
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("1992-03-01"),
},
@@ -618,7 +618,7 @@ func TestCachedTeamMDMConfig(t *testing.T) {
// saving a team updates config in cache
updateMDMConfig := fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("13.13.13"),
Deadline: optjson.SetString("2022-03-01"),
},
+1 -1
View File
@@ -41,7 +41,7 @@ CREATE TABLE `app_config_json` (
UNIQUE KEY `id` (`id`)
) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_setup\": {\"bootstrap_package\": null, \"macos_setup_assistant\": null, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `calendar_events` (
+18 -4
View File
@@ -591,10 +591,18 @@ func testTeamsMDMConfig(t *testing.T, ds *Datastore) {
Name: "team1",
Config: fleet.TeamConfig{
MDM: fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2025-10-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("11.11.11"),
Deadline: optjson.SetString("2024-04-04"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.12.12"),
Deadline: optjson.SetString("2023-03-03"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(7),
GracePeriodDays: optjson.SetInt(3),
@@ -614,10 +622,18 @@ func testTeamsMDMConfig(t *testing.T, ds *Datastore) {
require.NoError(t, err)
assert.Equal(t, &fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2025-10-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("11.11.11"),
Deadline: optjson.SetString("2024-04-04"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.12.12"),
Deadline: optjson.SetString("2023-03-03"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(7),
GracePeriodDays: optjson.SetInt(3),
@@ -695,7 +711,6 @@ func testTeamsNameEmoji(t *testing.T, ds *Datastore) {
assert.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, emoji1, results[0].Name)
}
// Ensure case-insensitive sort order for ames
@@ -717,5 +732,4 @@ func testTeamsNameSort(t *testing.T, ds *Datastore) {
for i, item := range teams {
assert.Equal(t, item.Name, results[i].Name)
}
}
+52
View File
@@ -54,6 +54,8 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeMDMUnenrolled{},
ActivityTypeEditedMacOSMinVersion{},
ActivityTypeEditedIOSMinVersion{},
ActivityTypeEditedIPadOSMinVersion{},
ActivityTypeEditedWindowsUpdates{},
ActivityTypeReadHostDiskEncryptionKey{},
@@ -837,6 +839,56 @@ func (a ActivityTypeEditedWindowsUpdates) Documentation() (activity string, deta
}`
}
type ActivityTypeEditedIOSMinVersion struct {
TeamID *uint `json:"team_id"`
TeamName *string `json:"team_name"`
MinimumVersion string `json:"minimum_version"`
Deadline string `json:"deadline"`
}
func (a ActivityTypeEditedIOSMinVersion) ActivityName() string {
return "edited_ios_min_version"
}
func (a ActivityTypeEditedIOSMinVersion) Documentation() (activity string, details string, detailsExample string) {
return `Generated when the minimum required iOS version or deadline is modified.`,
`This activity contains the following fields:
- "team_id": The ID of the team that the minimum iOS version applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
- "team_name": The name of the team that the minimum iOS version applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
- "minimum_version": The minimum iOS version required, empty if the requirement was removed.
- "deadline": The deadline by which the minimum version requirement must be applied, empty if the requirement was removed.`, `{
"team_id": 3,
"team_name": "iPhones",
"minimum_version": "17.5.1",
"deadline": "2023-06-01"
}`
}
type ActivityTypeEditedIPadOSMinVersion struct {
TeamID *uint `json:"team_id"`
TeamName *string `json:"team_name"`
MinimumVersion string `json:"minimum_version"`
Deadline string `json:"deadline"`
}
func (a ActivityTypeEditedIPadOSMinVersion) ActivityName() string {
return "edited_ipados_min_version"
}
func (a ActivityTypeEditedIPadOSMinVersion) Documentation() (activity string, details string, detailsExample string) {
return `Generated when the minimum required iPadOS version or deadline is modified.`,
`This activity contains the following fields:
- "team_id": The ID of the team that the minimum iPadOS version applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
- "team_name": The name of the team that the minimum iPadOS version applies to, ` + "`null`" + ` if it applies to devices that are not in a team.
- "minimum_version": The minimum iPadOS version required, empty if the requirement was removed.
- "deadline": The deadline by which the minimum version requirement must be applied, empty if the requirement was removed.`, `{
"team_id": 3,
"team_name": "iPads",
"minimum_version": "17.5.1",
"deadline": "2023-06-01"
}`
}
type ActivityTypeReadHostDiskEncryptionKey struct {
HostID uint `json:"host_id"`
HostDisplayName string `json:"host_display_name"`
+12 -5
View File
@@ -148,7 +148,13 @@ type MDM struct {
// backend, should be done only after careful analysis.
EnabledAndConfigured bool `json:"enabled_and_configured"`
MacOSUpdates MacOSUpdates `json:"macos_updates"`
// MacOSUpdates defines the OS update settings for macOS devices.
MacOSUpdates AppleOSUpdateSettings `json:"macos_updates"`
// IOSUpdates defines the OS update settings for iOS devices.
IOSUpdates AppleOSUpdateSettings `json:"ios_updates"`
// IPadOSUpdates defines the OS update settings for iPadOS devices.
IPadOSUpdates AppleOSUpdateSettings `json:"ipados_updates"`
// WindowsUpdates defines the OS update settings for Windows devices.
WindowsUpdates WindowsUpdates `json:"windows_updates"`
MacOSSettings MacOSSettings `json:"macos_settings"`
@@ -182,8 +188,9 @@ func (m MDM) AtLeastOnePlatformEnabledAndConfigured() bool {
// format only (no prerelease or build metadata).
var versionStringRegex = regexp.MustCompile(`^\d+(\.\d+)?(\.\d+)?$`)
// MacOSUpdates is part of AppConfig and defines the macOS update settings.
type MacOSUpdates struct {
// AppleOSUpdateSettings is the common type that contains the settings
// for OS updates on Apple devices.
type AppleOSUpdateSettings struct {
// MinimumVersion is the required minimum operating system version.
MinimumVersion optjson.String `json:"minimum_version"`
// Deadline the required installation date for Nudge to enforce the required
@@ -192,12 +199,12 @@ type MacOSUpdates struct {
}
// Configured returns a boolean indicating if updates are configured
func (m MacOSUpdates) Configured() bool {
func (m AppleOSUpdateSettings) Configured() bool {
return m.Deadline.Value != "" &&
m.MinimumVersion.Value != ""
}
func (m MacOSUpdates) Validate() error {
func (m AppleOSUpdateSettings) Validate() error {
// if no settings are provided it's okay to skip further validation
if m.MinimumVersion.Value == "" && m.Deadline.Value == "" {
// if one is set and empty, the other must be set and empty too, otherwise
+14 -16
View File
@@ -13,26 +13,26 @@ func TestMacOSUpdatesValidate(t *testing.T) {
t.Run("valid", func(t *testing.T) {
cases := []struct {
name string
m MacOSUpdates
m AppleOSUpdateSettings
}{
{"empty", MacOSUpdates{}},
{"empty", AppleOSUpdateSettings{}},
{
"with full version",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2020-01-01"),
},
},
{
"without patch version",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15"),
Deadline: optjson.SetString("2020-01-01"),
},
},
{
"only major version",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10"),
Deadline: optjson.SetString("2020-01-01"),
},
@@ -49,25 +49,25 @@ func TestMacOSUpdatesValidate(t *testing.T) {
t.Run("invalid deadline", func(t *testing.T) {
cases := []struct {
name string
m MacOSUpdates
m AppleOSUpdateSettings
}{
{
"version but no deadline",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString(""),
},
},
{
"deadline with timestamp",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2020-01-01T00:00:00Z"),
},
},
{
"incomplete date",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2020-01"),
},
@@ -84,25 +84,25 @@ func TestMacOSUpdatesValidate(t *testing.T) {
t.Run("invalid version", func(t *testing.T) {
cases := []struct {
name string
m MacOSUpdates
m AppleOSUpdateSettings
}{
{
"deadline but no version",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString(""),
Deadline: optjson.SetString("2020-01-01"),
},
},
{
"version with build info",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0 (19A583)"),
Deadline: optjson.SetString("2020-01-01"),
},
},
{
"version with patch info",
MacOSUpdates{
AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0-patch1"),
Deadline: optjson.SetString("2020-01-01"),
},
@@ -185,7 +185,7 @@ func TestMacOSUpdatesConfigured(t *testing.T) {
}
for _, tc := range cases {
m := MacOSUpdates{
m := AppleOSUpdateSettings{
MinimumVersion: optjson.SetString(tc.version),
Deadline: optjson.SetString(tc.deadline),
}
@@ -266,10 +266,8 @@ func TestAppConfigDeprecatedFields(t *testing.T) {
diskEncryption, exists := mdm["enable_disk_encryption"]
require.True(t, exists)
require.EqualValues(t, c.wantDiskEncryption, diskEncryption)
})
}
}
func TestAtLeastOnePlatformEnabledAndConfigured(t *testing.T) {
+17 -2
View File
@@ -28,11 +28,16 @@ type UserCalendar interface {
// CreateEvent, GetAndUpdateEvent and DeleteEvent reference the user's calendar.
Configure(userEmail string) error
// CreateEvent creates a new event on the calendar on the given date. DayEndedError is returned if there is no time left on the given date to schedule event.
CreateEvent(dateOfEvent time.Time, genBodyFn func(conflict bool) (body string, ok bool, err error)) (event *CalendarEvent, err error)
CreateEvent(
dateOfEvent time.Time,
genBodyFn func(conflict bool) (body string, ok bool, err error),
opts CalendarCreateEventOpts,
) (event *CalendarEvent, err error)
// GetAndUpdateEvent retrieves the event from the calendar.
// If the event has been modified, it returns the updated event.
// If the event has been deleted, it schedules a new event with given body callback and returns the new event.
GetAndUpdateEvent(event *CalendarEvent, genBodyFn func(conflict bool) (body string, ok bool, err error)) (updatedEvent *CalendarEvent,
GetAndUpdateEvent(event *CalendarEvent, genBodyFn func(conflict bool) (body string, ok bool, err error),
opts CalendarGetAndUpdateEventOpts) (updatedEvent *CalendarEvent,
updated bool, err error)
// DeleteEvent deletes the event with the given ID.
DeleteEvent(event *CalendarEvent) error
@@ -61,6 +66,16 @@ type Lock interface {
GetSet(ctx context.Context, key string) ([]string, error)
}
type CalendarCreateEventOpts struct {
EventUUID string
ChannelID string
ResourceID string
}
type CalendarGetAndUpdateEventOpts struct {
UpdateTimezone bool
}
type CalendarWebhookPayload struct {
Timestamp time.Time `json:"timestamp"`
HostID uint `json:"host_id"`
+7
View File
@@ -723,3 +723,10 @@ type VPPTokenData struct {
// structure of `VPPTokenRaw`.
Token string `json:"token"`
}
type AppleDevice int
const (
MacOS AppleDevice = iota
IOS
IPadOS
)
+1 -1
View File
@@ -44,7 +44,7 @@ type nudgeUpdateElements struct {
MainHeader string `json:"mainHeader"`
}
func NewNudgeConfig(macOSUpdates MacOSUpdates) (*NudgeConfig, error) {
func NewNudgeConfig(macOSUpdates AppleOSUpdateSettings) (*NudgeConfig, error) {
deadline, err := time.Parse("2006-01-02", macOSUpdates.Deadline.Value)
if err != nil {
return nil, err
+1 -1
View File
@@ -34,7 +34,7 @@ type EnterpriseOverrides struct {
DeleteMDMAppleBootstrapPackage func(ctx context.Context, teamID *uint) error
MDMWindowsEnableOSUpdates func(ctx context.Context, teamID *uint, updates WindowsUpdates) error
MDMWindowsDisableOSUpdates func(ctx context.Context, teamID *uint) error
MDMAppleEditedMacOSUpdates func(ctx context.Context, teamID *uint, updates MacOSUpdates) error
MDMAppleEditedAppleOSUpdates func(ctx context.Context, teamID *uint, appleDevice AppleDevice, updates AppleOSUpdateSettings) error
}
type OsqueryService interface {
+26 -10
View File
@@ -33,10 +33,18 @@ type TeamPayload struct {
// need to be able which part of the MDM config was provided in the request,
// so the fields are pointers to structs.
type TeamPayloadMDM struct {
EnableDiskEncryption optjson.Bool `json:"enable_disk_encryption"`
MacOSUpdates *MacOSUpdates `json:"macos_updates"`
WindowsUpdates *WindowsUpdates `json:"windows_updates"`
MacOSSetup *MacOSSetup `json:"macos_setup"`
EnableDiskEncryption optjson.Bool `json:"enable_disk_encryption"`
// MacOSUpdates defines the OS update settings for macOS devices.
MacOSUpdates *AppleOSUpdateSettings `json:"macos_updates"`
// IOSUpdates defines the OS update settings for iOS devices.
IOSUpdates *AppleOSUpdateSettings `json:"ios_updates"`
// IPadOSUpdates defines the OS update settings for iPadOS devices.
IPadOSUpdates *AppleOSUpdateSettings `json:"ipados_updates"`
// WindowsUpdates defines the OS update settings for Windows devices.
WindowsUpdates *WindowsUpdates `json:"windows_updates"`
MacOSSetup *MacOSSetup `json:"macos_setup"`
}
// Team is the data representation for the "Team" concept (group of hosts and
@@ -178,11 +186,13 @@ type TeamSpecSoftwarePackage struct {
}
type TeamMDM struct {
EnableDiskEncryption bool `json:"enable_disk_encryption"`
MacOSUpdates MacOSUpdates `json:"macos_updates"`
WindowsUpdates WindowsUpdates `json:"windows_updates"`
MacOSSettings MacOSSettings `json:"macos_settings"`
MacOSSetup MacOSSetup `json:"macos_setup"`
EnableDiskEncryption bool `json:"enable_disk_encryption"`
MacOSUpdates AppleOSUpdateSettings `json:"macos_updates"`
IOSUpdates AppleOSUpdateSettings `json:"ios_updates"`
IPadOSUpdates AppleOSUpdateSettings `json:"ipados_updates"`
WindowsUpdates WindowsUpdates `json:"windows_updates"`
MacOSSettings MacOSSettings `json:"macos_settings"`
MacOSSetup MacOSSetup `json:"macos_setup"`
WindowsSettings WindowsSettings `json:"windows_settings"`
// NOTE: TeamSpecMDM must be kept in sync with TeamMDM.
@@ -233,7 +243,13 @@ func (t *TeamMDM) Copy() *TeamMDM {
type TeamSpecMDM struct {
EnableDiskEncryption optjson.Bool `json:"enable_disk_encryption"`
MacOSUpdates MacOSUpdates `json:"macos_updates"`
// MacOSUpdates defines the OS update settings for macOS devices.
MacOSUpdates AppleOSUpdateSettings `json:"macos_updates"`
// IOSUpdates defines the OS update settings for iOS devices.
IOSUpdates AppleOSUpdateSettings `json:"ios_updates"`
// IPadOSUpdates defines the OS update settings for iPadOS devices.
IPadOSUpdates AppleOSUpdateSettings `json:"ipados_updates"`
// WindowsUpdates defines the OS update settings for Windows devices.
WindowsUpdates WindowsUpdates `json:"windows_updates"`
// A map is used for the macos settings so that we can easily detect if its
+1 -1
View File
@@ -269,7 +269,7 @@ func TestTeamMDMCopy(t *testing.T) {
t.Run("copy value fields", func(t *testing.T) {
tm := &TeamMDM{
EnableDiskEncryption: true,
MacOSUpdates: MacOSUpdates{
MacOSUpdates: AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.4"),
Deadline: optjson.SetString("2020-01-01"),
},
+27
View File
@@ -299,6 +299,33 @@ func (svc *MDMAppleCommander) DeviceConfigured(ctx context.Context, hostUUID, cm
return svc.EnqueueCommand(ctx, []string{hostUUID}, raw)
}
func (svc *MDMAppleCommander) DeviceInformation(ctx context.Context, hostUUIDs []string, cmdUUID string) error {
raw := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Command</key>
<dict>
<key>Queries</key>
<array>
<string>DeviceName</string>
<string>DeviceCapacity</string>
<string>AvailableDeviceCapacity</string>
<string>OSVersion</string>
<string>WiFiMAC</string>
<string>ProductName</string>
</array>
<key>RequestType</key>
<string>DeviceInformation</string>
</dict>
<key>CommandUUID</key>
<string>%s</string>
</dict>
</plist>`, cmdUUID)
return svc.EnqueueCommand(ctx, hostUUIDs, raw)
}
// EnqueueCommand takes care of enqueuing the commands and sending push
// notifications to the devices.
//
+15 -1
View File
@@ -101,6 +101,14 @@ const (
// FleetMacOSUpdatesProfileName is the name of the DDM profile used by Fleet
// to configure macOS OS updates.
FleetMacOSUpdatesProfileName = "Fleet macOS OS Updates"
// FleetIOSUpdatesProfileName is the name of the DDM profile used by Fleet
// to configure iOS OS updates.
FleetIOSUpdatesProfileName = "Fleet iOS OS Updates"
// FleetIPadOSUpdatesProfileName is the name of the DDM profile used by Fleet
// to configure iPadOS OS updates.
FleetIPadOSUpdatesProfileName = "Fleet iPadOS OS Updates"
)
// FleetReservedProfileNames returns a map of PayloadDisplayName or profile
@@ -111,6 +119,8 @@ func FleetReservedProfileNames() map[string]struct{} {
FleetFileVaultProfileName: {},
FleetWindowsOSUpdatesProfileName: {},
FleetMacOSUpdatesProfileName: {},
FleetIOSUpdatesProfileName: {},
FleetIPadOSUpdatesProfileName: {},
FleetCAConfigProfileName: {},
}
}
@@ -130,5 +140,9 @@ func ListFleetReservedMacOSProfileNames() []string {
// ListFleetReservedMacOSDeclarationNames returns a list of declaration names
// that are reserved by Fleet for Apple DDM declarations.
func ListFleetReservedMacOSDeclarationNames() []string {
return []string{FleetMacOSUpdatesProfileName}
return []string{
FleetMacOSUpdatesProfileName,
FleetIOSUpdatesProfileName,
FleetIPadOSUpdatesProfileName,
}
}
+82 -24
View File
@@ -558,27 +558,26 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
}
}
// if the macOS minimum version requirement changed, create the corresponding
// activity
if oldAppConfig.MDM.MacOSUpdates.MinimumVersion.Value != appConfig.MDM.MacOSUpdates.MinimumVersion.Value ||
oldAppConfig.MDM.MacOSUpdates.Deadline.Value != appConfig.MDM.MacOSUpdates.Deadline.Value {
if license.IsPremium() {
// macOS updates are premium feature
if err := svc.EnterpriseOverrides.MDMAppleEditedMacOSUpdates(ctx, nil, appConfig.MDM.MacOSUpdates); err != nil {
return nil, ctxerr.Wrap(ctx, err, "update DDM profile after macOS updates change")
}
}
if err := svc.NewActivity(
ctx,
authz.UserFromContext(ctx),
fleet.ActivityTypeEditedMacOSMinVersion{
MinimumVersion: appConfig.MDM.MacOSUpdates.MinimumVersion.Value,
Deadline: appConfig.MDM.MacOSUpdates.Deadline.Value,
},
); err != nil {
return nil, ctxerr.Wrap(ctx, err, "create activity for app config macos min version modification")
}
//
// Process OS updates config changes for Apple devices.
//
if err := svc.processAppleOSUpdateSettings(ctx, license, fleet.MacOS,
oldAppConfig.MDM.MacOSUpdates,
appConfig.MDM.MacOSUpdates,
); err != nil {
return nil, ctxerr.Wrap(ctx, err, "process macOS OS updates config change")
}
if err := svc.processAppleOSUpdateSettings(ctx, license, fleet.IOS,
oldAppConfig.MDM.IOSUpdates,
appConfig.MDM.IOSUpdates,
); err != nil {
return nil, ctxerr.Wrap(ctx, err, "process iOS OS updates config change")
}
if err := svc.processAppleOSUpdateSettings(ctx, license, fleet.IPadOS,
oldAppConfig.MDM.IPadOSUpdates,
appConfig.MDM.IPadOSUpdates,
); err != nil {
return nil, ctxerr.Wrap(ctx, err, "process iPadOS OS updates config change")
}
// if the Windows updates requirements changed, create the corresponding
@@ -670,6 +669,47 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
return obfuscatedAppConfig, nil
}
// processAppleOSUpdateSettings updates the OS updates configuration if the minimum version+deadline are updated.
func (svc *Service) processAppleOSUpdateSettings(
ctx context.Context,
license *fleet.LicenseInfo,
appleDevice fleet.AppleDevice,
oldOSUpdateSettings fleet.AppleOSUpdateSettings,
newOSUpdateSettings fleet.AppleOSUpdateSettings,
) error {
if oldOSUpdateSettings.MinimumVersion.Value != newOSUpdateSettings.MinimumVersion.Value ||
oldOSUpdateSettings.Deadline.Value != newOSUpdateSettings.Deadline.Value {
if license.IsPremium() {
if err := svc.EnterpriseOverrides.MDMAppleEditedAppleOSUpdates(ctx, nil, appleDevice, newOSUpdateSettings); err != nil {
return ctxerr.Wrap(ctx, err, "update DDM profile after Apple OS updates change")
}
}
var activity fleet.ActivityDetails
switch appleDevice {
case fleet.MacOS:
activity = fleet.ActivityTypeEditedMacOSMinVersion{
MinimumVersion: newOSUpdateSettings.MinimumVersion.Value,
Deadline: newOSUpdateSettings.Deadline.Value,
}
case fleet.IOS:
activity = fleet.ActivityTypeEditedIOSMinVersion{
MinimumVersion: newOSUpdateSettings.MinimumVersion.Value,
Deadline: newOSUpdateSettings.Deadline.Value,
}
case fleet.IPadOS:
activity = fleet.ActivityTypeEditedIPadOSMinVersion{
MinimumVersion: newOSUpdateSettings.MinimumVersion.Value,
Deadline: newOSUpdateSettings.Deadline.Value,
}
}
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), activity); err != nil {
return ctxerr.Wrap(ctx, err, "create activity for app config apple min version modification")
}
}
return nil
}
func (svc *Service) HasCustomSetupAssistantConfigurationWebURL(ctx context.Context, teamID *uint) (bool, error) {
az, ok := authz_ctx.FromContext(ctx)
if !ok || !az.Checked() {
@@ -784,12 +824,24 @@ func (svc *Service) validateMDM(
}
// MacOSUpdates
updatingVersion := mdm.MacOSUpdates.MinimumVersion.Value != "" &&
updatingMacOSVersion := mdm.MacOSUpdates.MinimumVersion.Value != "" &&
mdm.MacOSUpdates.MinimumVersion != oldMdm.MacOSUpdates.MinimumVersion
updatingDeadline := mdm.MacOSUpdates.Deadline.Value != "" &&
updatingMacOSDeadline := mdm.MacOSUpdates.Deadline.Value != "" &&
mdm.MacOSUpdates.Deadline != oldMdm.MacOSUpdates.Deadline
// IOSUpdates
updatingIOSVersion := mdm.IOSUpdates.MinimumVersion.Value != "" &&
mdm.IOSUpdates.MinimumVersion != oldMdm.IOSUpdates.MinimumVersion
updatingIOSDeadline := mdm.IOSUpdates.Deadline.Value != "" &&
mdm.IOSUpdates.Deadline != oldMdm.IOSUpdates.Deadline
// IPadOSUpdates
updatingIPadOSVersion := mdm.IPadOSUpdates.MinimumVersion.Value != "" &&
mdm.IPadOSUpdates.MinimumVersion != oldMdm.IPadOSUpdates.MinimumVersion
updatingIPadOSDeadline := mdm.IPadOSUpdates.Deadline.Value != "" &&
mdm.IPadOSUpdates.Deadline != oldMdm.IPadOSUpdates.Deadline
if updatingVersion || updatingDeadline {
if updatingMacOSVersion || updatingMacOSDeadline ||
updatingIOSVersion || updatingIOSDeadline ||
updatingIPadOSVersion || updatingIPadOSDeadline {
// TODO: Should we validate MDM configured on here too?
if !license.IsPremium() {
@@ -800,6 +852,12 @@ func (svc *Service) validateMDM(
if err := mdm.MacOSUpdates.Validate(); err != nil {
invalid.Append("macos_updates", err.Error())
}
if err := mdm.IOSUpdates.Validate(); err != nil {
invalid.Append("ios_updates", err.Error())
}
if err := mdm.IPadOSUpdates.Validate(); err != nil {
invalid.Append("ipados_updates", err.Error())
}
// WindowsUpdates
updatingWindowsUpdates := !mdm.WindowsUpdates.Equal(oldMdm.WindowsUpdates)
+18 -6
View File
@@ -859,7 +859,9 @@ func TestMDMAppleConfig(t *testing.T) {
licenseTier: "free",
expectedMDM: fleet.MDM{
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -889,7 +891,9 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
AppleBMDefaultTeam: "foobar",
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -904,7 +908,9 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
AppleBMDefaultTeam: "foobar",
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -925,7 +931,9 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: fleet.SSOProviderSettings{EntityID: "foo"}},
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -949,7 +957,9 @@ func TestMDMAppleConfig(t *testing.T) {
IDPName: "onelogin",
}},
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
@@ -1007,7 +1017,9 @@ func TestMDMAppleConfig(t *testing.T) {
expectedMDM: fleet.MDM{
EnableDiskEncryption: optjson.Bool{Set: true, Valid: true, Value: false},
MacOSSetup: fleet.MacOSSetup{BootstrapPackage: optjson.String{Set: true}, MacOSSetupAssistant: optjson.String{Set: true}, EnableReleaseDeviceManually: optjson.SetBool(false)},
MacOSUpdates: fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}},
WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}},
WindowsSettings: fleet.WindowsSettings{
CustomSettings: optjson.Slice[fleet.MDMProfileSpec]{Set: true, Value: []fleet.MDMProfileSpec{}},
+5 -4
View File
@@ -2752,6 +2752,7 @@ func (svc *MDMAppleCheckinAndCommandService) CommandAndReportResults(r *mdm.Requ
host.PrimaryMac = wifiMac
host.HardwareModel = productName
host.DetailUpdatedAt = time.Now()
host.RefetchRequested = false
if err := svc.ds.UpdateHost(r.Context, host); err != nil {
return nil, ctxerr.Wrap(r.Context, err, "failed to update host")
}
@@ -2805,7 +2806,7 @@ func (svc *MDMAppleCheckinAndCommandService) CommandAndReportResults(r *mdm.Requ
// set "pending-install" profiles to "verifying" or "failed"
// depending on the status of the DeviceManagement command
status := mdmAppleDeliveryStatusFromCommandStatus(cmdResult.Status)
detail := fmt.Sprintf("%s. Make sure the host is on macOS 13 or higher.", apple_mdm.FmtErrorChain(cmdResult.ErrorChain))
detail := fmt.Sprintf("%s. Make sure the host is on macOS 13+, iOS 17+, iPadOS 17+.", apple_mdm.FmtErrorChain(cmdResult.ErrorChain))
err := svc.ds.MDMAppleSetPendingDeclarationsAs(r.Context, cmdResult.UDID, status, detail)
return nil, ctxerr.Wrap(r.Context, err, "update declaration status on DeclarativeManagement ack")
case "InstallApplication":
@@ -3276,7 +3277,7 @@ func ReconcileAppleProfiles(
// scepCertRenewalThresholdDays defines the number of days before a SCEP
// certificate must be renewed.
const scepCertRenewalThresholdDays = 30
const scepCertRenewalThresholdDays = 180
// maxCertsRenewalPerRun specifies the maximum number of certificates to renew
// in a single cron run.
@@ -3285,8 +3286,8 @@ const scepCertRenewalThresholdDays = 30
// day, and we have room for 24,000 * scepCertRenewalThresholdDays total
// renewals.
//
// For a default of 30 days as a threshold this gives us room for a fleet of
// 720,000 devices expiring at the same time.
// For a default of 180 days as a threshold this gives us room for a fleet of
// ~4 million devices expiring at the same time.
const maxCertsRenewalPerRun = 100
func RenewSCEPCertificates(
+37 -35
View File
@@ -2,53 +2,55 @@ package service
import (
"context"
"net/http"
"net/url"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/gorilla/mux"
)
// Disabling the calendarWebhookEndpoint to address bugs
// type calendarWebhookRequest struct {
// eventUUID string
// googleChannelID string
// googleResourceState string
// }
type calendarWebhookRequest struct {
eventUUID string
googleChannelID string
googleResourceState string
}
// DecodeRequest implement requestDecoder interface to take full control of decoding the request
// func (calendarWebhookRequest) DecodeRequest(_ context.Context, r *http.Request) (interface{}, error) {
// var req calendarWebhookRequest
// eventUUID, ok := mux.Vars(r)["event_uuid"]
// if !ok {
// return nil, errBadRoute
// }
// unescaped, err := url.PathUnescape(eventUUID)
// if err != nil {
// return "", ctxerr.Wrap(r.Context(), err, "unescape value in path")
// }
// req.eventUUID = unescaped
func (calendarWebhookRequest) DecodeRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req calendarWebhookRequest
eventUUID, ok := mux.Vars(r)["event_uuid"]
if !ok {
return nil, errBadRoute
}
unescaped, err := url.PathUnescape(eventUUID)
if err != nil {
return "", ctxerr.Wrap(r.Context(), err, "unescape value in path")
}
req.eventUUID = unescaped
// req.googleChannelID = r.Header.Get("X-Goog-Channel-Id")
// req.googleResourceState = r.Header.Get("X-Goog-Resource-State")
req.googleChannelID = r.Header.Get("X-Goog-Channel-Id")
req.googleResourceState = r.Header.Get("X-Goog-Resource-State")
// return &req, nil
// }
return &req, nil
}
// type calendarWebhookResponse struct {
// Err error `json:"error,omitempty"`
// }
type calendarWebhookResponse struct {
Err error `json:"error,omitempty"`
}
// func (r calendarWebhookResponse) error() error { return r.Err }
func (r calendarWebhookResponse) error() error { return r.Err }
// func calendarWebhookEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
// req := request.(*calendarWebhookRequest)
// err := svc.CalendarWebhook(ctx, req.eventUUID, req.googleChannelID, req.googleResourceState)
// if err != nil {
// return calendarWebhookResponse{Err: err}, err
// }
func calendarWebhookEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*calendarWebhookRequest)
err := svc.CalendarWebhook(ctx, req.eventUUID, req.googleChannelID, req.googleResourceState)
if err != nil {
return calendarWebhookResponse{Err: err}, err
}
// resp := calendarWebhookResponse{}
// return resp, nil
// }
resp := calendarWebhookResponse{}
return resp, nil
}
func (svc *Service) CalendarWebhook(ctx context.Context, eventUUID string, channelID string, resourceState string) error {
// skipauth: No authorization check needed due to implementation returning only license error.
+20
View File
@@ -8,6 +8,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/fleetdm/fleet/v4/ee/server/calendar"
"github.com/fleetdm/fleet/v4/server/config"
@@ -19,9 +20,28 @@ import (
const (
LockKeyPrefix = "calendar:lock:"
ReservedLockKeyPrefix = "calendar:reserved:"
RecentUpdateKeyPrefix = "calendar:recent_update:"
QueueKey = "calendar:queue"
// DistributedLockExpireMs is the time Redis will hold the lock before automatically releasing it.
// Our current max retry time for calendar API is 10 minutes, and multiple API calls (with their own retry timing) can be made during event processing.
// If a Fleet server gets the lock and is then shut down before releasing the lock, the next server may need to wait this long
// before getting the lock.
DistributedLockExpireMs = 20 * 60 * 1000
// ReserveLockExpireMs is used by cron job to guarantee that it gets the next lock.
ReserveLockExpireMs = 2 * DistributedLockExpireMs
// RecentCalendarUpdateValue is the value stored in Redis to indicate that a calendar event was recently updated.
RecentCalendarUpdateValue = "1"
)
// RecentCalendarUpdateDuration is the duration during which we will ignore a calendar event callback if the event in DB was just updated by a previous callback.
// This reduces CPU load and Google API load. If we update the event, Google calendar may send a callback which we don't need to process.
// We are using Redis instead of updated_at timestamp in DB because the calendar cron job may update the timestamp even when the event did not change, which could
// cause us to miss a legitimate update.
// This variable is exposed so that it can be modified by unit tests.
var RecentCalendarUpdateDuration = 10 * time.Second
type Config struct {
config.CalendarConfig
fleet.GoogleCalendarIntegration
+26
View File
@@ -1298,6 +1298,32 @@ func (c *Client) DoGitOps(
if deadline, ok := macOSUpdates["deadline"]; !ok || deadline == nil {
macOSUpdates["deadline"] = ""
}
// Put in default values for ios_updates
if config.Controls.IOSUpdates != nil {
mdmAppConfig["ios_updates"] = config.Controls.IOSUpdates
} else {
mdmAppConfig["ios_updates"] = map[string]interface{}{}
}
iOSUpdates := mdmAppConfig["ios_updates"].(map[string]interface{})
if minimumVersion, ok := iOSUpdates["minimum_version"]; !ok || minimumVersion == nil {
iOSUpdates["minimum_version"] = ""
}
if deadline, ok := iOSUpdates["deadline"]; !ok || deadline == nil {
iOSUpdates["deadline"] = ""
}
// Put in default values for ipados_updates
if config.Controls.IPadOSUpdates != nil {
mdmAppConfig["ipados_updates"] = config.Controls.IPadOSUpdates
} else {
mdmAppConfig["ipados_updates"] = map[string]interface{}{}
}
iPadOSUpdates := mdmAppConfig["ipados_updates"].(map[string]interface{})
if minimumVersion, ok := iPadOSUpdates["minimum_version"]; !ok || minimumVersion == nil {
iPadOSUpdates["minimum_version"] = ""
}
if deadline, ok := iPadOSUpdates["deadline"]; !ok || deadline == nil {
iPadOSUpdates["deadline"] = ""
}
// Put in default values for macos_setup
if config.Controls.MacOSSetup != nil {
mdmAppConfig["macos_setup"] = config.Controls.MacOSSetup
+1 -2
View File
@@ -950,8 +950,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ne.HEAD("/api/fleet/orbit/ping", orbitPingEndpoint, orbitPingRequest{})
// This is a callback endpoint for calendar integration -- it is called to notify an event change in a user calendar
// Disabling the calendarWebhookEndpoint to address bugs
// ne.POST("/api/_version_/fleet/calendar/webhook/{event_uuid}", calendarWebhookEndpoint, calendarWebhookRequest{})
ne.POST("/api/_version_/fleet/calendar/webhook/{event_uuid}", calendarWebhookEndpoint, calendarWebhookRequest{})
neAppleMDM.WithCustomMiddleware(limiter.Limit("login", throttled.RateQuota{MaxRate: loginRateLimit, MaxBurst: 9})).
POST("/api/_version_/fleet/mdm/sso", initiateMDMAppleSSOEndpoint, initiateMDMAppleSSORequest{})
+36 -2
View File
@@ -6,6 +6,7 @@ import (
"crypto/tls"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@@ -30,6 +31,7 @@ import (
"github.com/fleetdm/fleet/v4/server/worker"
"github.com/go-kit/log/level"
"github.com/gocarina/gocsv"
"github.com/google/uuid"
)
// HostDetailResponse is the response struct that contains the full host information
@@ -1008,12 +1010,15 @@ func refetchHostEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
}
func (svc *Service) RefetchHost(ctx context.Context, id uint) error {
var host *fleet.Host
// iOS and iPadOS refetch are not authenticated with device token because these devices do not have Fleet Desktop
if !svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnDeviceToken) {
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
var err error
if err = svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return err
}
host, err := svc.ds.HostLite(ctx, id)
host, err = svc.ds.HostLite(ctx, id)
if err != nil {
return ctxerr.Wrap(ctx, err, "find host for refetch")
}
@@ -1025,6 +1030,17 @@ func (svc *Service) RefetchHost(ctx context.Context, id uint) error {
}
}
if host != nil && (host.Platform == "ios" || host.Platform == "ipados") {
err := svc.verifyMDMConfiguredAndConnected(ctx, host)
if err != nil {
return err
}
err = svc.mdmAppleCommander.DeviceInformation(ctx, []string{host.UUID}, fleet.RefetchCommandUUIDPrefix+uuid.NewString())
if err != nil {
return ctxerr.Wrap(ctx, err, "refetch host with MDM")
}
}
if err := svc.ds.UpdateHostRefetchRequested(ctx, id, true); err != nil {
return ctxerr.Wrap(ctx, err, "save host")
}
@@ -1032,6 +1048,24 @@ func (svc *Service) RefetchHost(ctx context.Context, id uint) error {
return nil
}
func (svc *Service) verifyMDMConfiguredAndConnected(ctx context.Context, host *fleet.Host) error {
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
if errors.Is(err, fleet.ErrMDMNotConfigured) {
err = fleet.NewInvalidArgumentError("id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
}
return ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
}
connected, err := svc.ds.IsHostConnectedToFleetMDM(ctx, host)
if err != nil {
return ctxerr.Wrap(ctx, err, "checking if host is connected to Fleet")
}
if !connected {
return ctxerr.Wrap(ctx,
fleet.NewInvalidArgumentError("id", "Host does not have MDM turned on."))
}
return nil
}
func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts fleet.HostDetailOptions) (*fleet.HostDetail, error) {
if !opts.ExcludeSoftware {
if err := svc.ds.LoadHostSoftware(ctx, host, opts.IncludeCVEScores); err != nil {
+425 -50
View File
@@ -169,6 +169,14 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
"minimum_version": "10.15.0",
"deadline": "2021-01-01",
},
"ios_updates": map[string]any{
"minimum_version": "17.5.1",
"deadline": "2024-07-23",
},
"ipados_updates": map[string]any{
"minimum_version": "18.0",
"deadline": "2024-08-24",
},
},
},
},
@@ -188,10 +196,18 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
AdditionalQueries: ptr.RawMessage(json.RawMessage(`{"foo": "bar"}`)),
}, team.Config.Features)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("17.5.1"),
Deadline: optjson.SetString("2024-07-23"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("18.0"),
Deadline: optjson.SetString("2024-08-24"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.Int{Set: true},
GracePeriodDays: optjson.Int{Set: true},
@@ -285,10 +301,18 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
require.NoError(t, err)
require.Equal(t, applyResp.TeamIDsByName[teamName], team.ID)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("17.5.1"),
Deadline: optjson.SetString("2024-07-23"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("18.0"),
Deadline: optjson.SetString("2024-08-24"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(1),
GracePeriodDays: optjson.SetInt(1),
@@ -307,10 +331,18 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
var getTmResp getTeamResponse
s.DoJSON("GET", "/api/latest/fleet/teams/"+fmt.Sprint(team.ID), nil, http.StatusOK, &getTmResp)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("17.5.1"),
Deadline: optjson.SetString("2024-07-23"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("18.0"),
Deadline: optjson.SetString("2024-08-24"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(1),
GracePeriodDays: optjson.SetInt(1),
@@ -331,10 +363,18 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
require.True(t, len(listTmResp.Teams) > 0)
require.Equal(t, team.ID, listTmResp.Teams[0].ID)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("17.5.1"),
Deadline: optjson.SetString("2024-07-23"),
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("18.0"),
Deadline: optjson.SetString("2024-08-24"),
},
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(1),
GracePeriodDays: optjson.SetInt(1),
@@ -2133,7 +2173,15 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() {
var getTmResp getTeamResponse
s.DoJSON("GET", "/api/latest/fleet/teams/"+fmt.Sprint(team.ID), nil, http.StatusOK, &getTmResp)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.String{Set: true},
Deadline: optjson.String{Set: true},
},
IOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.String{Set: true},
Deadline: optjson.String{Set: true},
},
IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.String{Set: true},
Deadline: optjson.String{Set: true},
},
@@ -2173,7 +2221,7 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() {
tmResp = teamResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"macos_updates": &fleet.MacOSUpdates{
"macos_updates": &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
},
@@ -2279,7 +2327,7 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() {
}, http.StatusUnprocessableEntity, &tmResp)
}
func (s *integrationEnterpriseTestSuite) assertMacOSUpdatesDeclaration(teamID *uint, expected *fleet.MacOSUpdates) {
func (s *integrationEnterpriseTestSuite) assertAppleOSUpdatesDeclaration(teamID *uint, profileName string, expected *fleet.AppleOSUpdateSettings) {
t := s.T()
if teamID == nil {
teamID = ptr.Uint(0)
@@ -2288,7 +2336,7 @@ func (s *integrationEnterpriseTestSuite) assertMacOSUpdatesDeclaration(teamID *u
var declUUID string
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
err := sqlx.GetContext(context.Background(), q, &declUUID,
`SELECT declaration_uuid FROM mdm_apple_declarations WHERE team_id = ? AND name = ?`, teamID, mdm.FleetMacOSUpdatesProfileName)
`SELECT declaration_uuid FROM mdm_apple_declarations WHERE team_id = ? AND name = ?`, teamID, profileName)
if expected == nil {
require.Error(t, err)
return nil
@@ -2307,10 +2355,9 @@ func (s *integrationEnterpriseTestSuite) assertMacOSUpdatesDeclaration(teamID *u
require.Contains(t, string(decl.RawJSON), fmt.Sprintf(`"TargetLocalDateTime": "%sT12:00:00"`, expected.Deadline.Value))
}
func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() {
t := s.T()
// Create a team
team := &fleet.Team{
Name: t.Name(),
Description: "Team description",
@@ -2322,41 +2369,92 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
team.ID = tmResp.Team.ID
// no OS updates settings at the moment
s.assertMacOSUpdatesDeclaration(&team.ID, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, nil)
// modify the team's config
updates := &fleet.MacOSUpdates{
// modify the team's config (macOS first)
macOSUpdates := &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2021-01-01"),
}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"macos_updates": updates,
"macos_updates": macOSUpdates,
},
}, http.StatusOK, &tmResp)
require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name), 0)
s.assertMacOSUpdatesDeclaration(&team.ID, updates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, nil)
// only update the deadline
updates = &fleet.MacOSUpdates{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2025-10-01"),
// modify the team's config (now iOS and iPadOS)
iOSUpdates := &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("11.11.11"),
Deadline: optjson.SetString("2022-02-02"),
}
iPadOSUpdates := &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.12.12"),
Deadline: optjson.SetString("2023-03-03"),
}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"macos_updates": updates,
"ios_updates": iOSUpdates,
"ipados_updates": iPadOSUpdates,
},
}, http.StatusOK, &tmResp)
require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2022-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2023-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name), 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "11.11.11", "deadline": "2022-02-02"}`, team.ID, team.Name), 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "12.12.12", "deadline": "2023-03-03"}`, team.ID, team.Name), 0)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates)
// only update the deadlines
macOSUpdates = &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.15.0"),
Deadline: optjson.SetString("2025-10-01"),
}
iOSUpdates = &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("11.11.11"),
Deadline: optjson.SetString("2024-02-02"),
}
iPadOSUpdates = &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.12.12"),
Deadline: optjson.SetString("2024-03-03"),
}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"macos_updates": macOSUpdates,
"ios_updates": iOSUpdates,
"ipados_updates": iPadOSUpdates,
},
}, http.StatusOK, &tmResp)
require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
lastActivity := s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2025-10-01"}`, team.ID, team.Name), 0)
require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value)
macOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2025-10-01"}`, team.ID, team.Name), 0)
iOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "11.11.11", "deadline": "2024-02-02"}`, team.ID, team.Name), 0)
iPadOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "12.12.12", "deadline": "2024-03-03"}`, team.ID, team.Name), 0)
s.assertMacOSUpdatesDeclaration(&team.ID, updates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates)
// setting the windows updates doesn't alter the macos updates
// setting the windows updates doesn't alter the apple updates
tmResp = teamResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
@@ -2368,13 +2466,21 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
}, http.StatusOK, &tmResp)
require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value)
require.Equal(t, 10, tmResp.Team.Config.MDM.WindowsUpdates.DeadlineDays.Value)
require.Equal(t, 2, tmResp.Team.Config.MDM.WindowsUpdates.GracePeriodDays.Value)
// did not create a new activity for macos updates
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), "", lastActivity)
lastActivity = s.lastActivityMatches(fleet.ActivityTypeEditedWindowsUpdates{}.ActivityName(), ``, 0)
// did not create a new activity for os updates
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), "", macOSLastActivity)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), "", iOSLastActivity)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), "", iPadOSLastActivity)
lastActivity := s.lastActivityMatches(fleet.ActivityTypeEditedWindowsUpdates{}.ActivityName(), ``, 0)
s.assertMacOSUpdatesDeclaration(&team.ID, updates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates)
// sending a nil MDM or MacOSUpdate config doesn't modify anything
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2387,10 +2493,16 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
}, http.StatusOK, &tmResp)
require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value)
// no new activity is created
s.lastActivityMatches("", "", lastActivity)
s.assertMacOSUpdatesDeclaration(&team.ID, updates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates)
// sending macos settings but no macos_updates does not change the macos updates
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2405,22 +2517,40 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
// no new activity is created
s.lastActivityMatches("", "", lastActivity)
s.assertMacOSUpdatesDeclaration(&team.ID, updates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates)
// sending empty MacOSUpdate fields empties both fields
// sending empty apple os updates fields empties both fields and removes the DDM profiles
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"macos_updates": map[string]any{
"minimum_version": "",
"deadline": nil,
},
"ios_updates": map[string]any{
"minimum_version": "",
"deadline": nil,
},
"ipados_updates": map[string]any{
"minimum_version": "",
"deadline": nil,
},
},
}, http.StatusOK, &tmResp)
require.Empty(t, tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value)
require.Empty(t, tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value)
s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "", "deadline": ""}`, team.ID, team.Name), 0)
require.Empty(t, tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value)
require.Empty(t, tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value)
require.Empty(t, tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Empty(t, tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "", "deadline": ""}`, team.ID, team.Name), 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "", "deadline": ""}`, team.ID, team.Name), 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "", "deadline": ""}`, team.ID, team.Name), 0)
s.assertMacOSUpdatesDeclaration(&team.ID, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, nil)
// error checks:
@@ -2433,6 +2563,22 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"minimum_version": "10.15.0",
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"minimum_version": "10.15.0",
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
// try to set an invalid minimum version
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2443,6 +2589,22 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"minimum_version": "10.15.0 (19A583)",
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"minimum_version": "10.15.0 (19A583)",
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
// try to set a deadline but not a minimum version
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2452,6 +2614,20 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"deadline": "2021-01-01T00:00:00Z",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
// try to set an empty deadline but not a minimum version
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2461,6 +2637,20 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"deadline": "",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"deadline": "",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
// try to set a minimum version but not a deadline
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2470,6 +2660,20 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"minimum_version": "10.15.0 (19A583)",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"minimum_version": "10.15.0 (19A583)",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
// try to set an empty minimum version but not a deadline
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
@@ -2479,6 +2683,20 @@ func (s *integrationEnterpriseTestSuite) TestMacOSUpdatesTeamConfig() {
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ios_updates": map[string]any{
"minimum_version": "",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{
"mdm": map[string]any{
"ipados_updates": map[string]any{
"minimum_version": "",
},
},
}, http.StatusUnprocessableEntity, &tmResp)
}
func (s *integrationEnterpriseTestSuite) TestListDevicePolicies() {
@@ -2868,7 +3086,7 @@ func (s *integrationEnterpriseTestSuite) TestMDMWindowsUpdates() {
s.lastActivityMatches("", ``, lastActivity)
}
func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() {
t := s.T()
// keep the last activity, to detect newly created ones
@@ -2887,7 +3105,7 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
// get the appconfig, nothing changed
acResp = appConfigResponse{}
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
require.Equal(t, fleet.MacOSUpdates{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, acResp.MDM.MacOSUpdates)
require.Equal(t, fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, acResp.MDM.MacOSUpdates)
// no activity got created
activitiesResp = listActivitiesResponse{}
@@ -2904,6 +3122,16 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"deadline": "2022-01-01"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ios_updates": {
"deadline": "2022-01-01"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ipados_updates": {
"deadline": "2022-01-01"
}
}}`)
// missing deadline
checkInvalidConfig(`{"mdm": {
@@ -2911,6 +3139,16 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"minimum_version": "12.1.1"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ios_updates": {
"minimum_version": "12.1.1"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ipados_updates": {
"minimum_version": "12.1.1"
}
}}`)
// invalid deadline
checkInvalidConfig(`{"mdm": {
@@ -2919,6 +3157,18 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"deadline": "2022"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ios_updates": {
"minimum_version": "12.1.1",
"deadline": "2022"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ipados_updates": {
"minimum_version": "12.1.1",
"deadline": "2022"
}
}}`)
// deadline includes timestamp
checkInvalidConfig(`{"mdm": {
@@ -2927,6 +3177,18 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"deadline": "2022-01-01T00:00:00Z"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ios_updates": {
"minimum_version": "12.1.1",
"deadline": "2022-01-01T00:00:00Z"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ipados_updates": {
"minimum_version": "12.1.1",
"deadline": "2022-01-01T00:00:00Z"
}
}}`)
// minimum_version includes build info
checkInvalidConfig(`{"mdm": {
@@ -2935,6 +3197,18 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"deadline": "2022-01-01"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ios_updates": {
"minimum_version": "12.1.1 (ABCD)",
"deadline": "2022-01-01"
}
}}`)
checkInvalidConfig(`{"mdm": {
"ipados_updates": {
"minimum_version": "12.1.1 (ABCD)",
"deadline": "2022-01-01"
}
}}`)
// valid config
acResp := appConfigResponse{}
@@ -2943,23 +3217,47 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
},
"ios_updates": {
"minimum_version": "13.13.13",
"deadline": "2023-03-03"
},
"ipados_updates": {
"minimum_version": "14.14.14",
"deadline": "2024-04-04"
}
}
}`), http.StatusOK, &acResp)
require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2022-01-01", acResp.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2023-03-03", acResp.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-04-04", acResp.MDM.IPadOSUpdates.Deadline.Value)
// edited macos min version activity got created
s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2022-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0)
s.assertMacOSUpdatesDeclaration(nil, &fleet.MacOSUpdates{
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2022-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2023-03-03", "minimum_version":"13.13.13", "team_id": null, "team_name": null}`, 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2024-04-04", "minimum_version":"14.14.14", "team_id": null, "team_name": null}`, 0)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2022-01-01"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2023-03-03"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2024-04-04"),
})
// get the appconfig
acResp = appConfigResponse{}
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2022-01-01", acResp.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2023-03-03", acResp.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-04-04", acResp.MDM.IPadOSUpdates.Deadline.Value)
// update the deadline
acResp = appConfigResponse{}
@@ -2968,17 +3266,37 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
"macos_updates": {
"minimum_version": "12.3.1",
"deadline": "2024-01-01"
},
"ios_updates": {
"minimum_version": "13.13.13",
"deadline": "2025-05-05"
},
"ipados_updates": {
"minimum_version": "14.14.14",
"deadline": "2026-06-06"
}
}
}`), http.StatusOK, &acResp)
require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value)
require.Equal(t, "2024-01-01", acResp.MDM.MacOSUpdates.Deadline.Value)
require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value)
require.Equal(t, "2025-05-05", acResp.MDM.IOSUpdates.Deadline.Value)
require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Equal(t, "2026-06-06", acResp.MDM.IPadOSUpdates.Deadline.Value)
// another edited macos min version activity got created
lastActivity = s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2024-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0)
s.assertMacOSUpdatesDeclaration(nil, &fleet.MacOSUpdates{
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2024-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2025-05-05", "minimum_version":"13.13.13", "team_id": null, "team_name": null}`, 0)
lastActivity = s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2026-06-06", "minimum_version":"14.14.14", "team_id": null, "team_name": null}`, 0)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2024-01-01"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2025-05-05"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2026-06-06"),
})
// update something unrelated - the transparency url
acResp = appConfigResponse{}
@@ -2988,43 +3306,81 @@ func (s *integrationEnterpriseTestSuite) TestMDMMacOSUpdates() {
// no activity got created
s.lastActivityMatches("", ``, lastActivity)
s.assertMacOSUpdatesDeclaration(nil, &fleet.MacOSUpdates{
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2024-01-01"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2025-05-05"),
})
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2026-06-06"),
})
// clear the macos requirement
// clear the apple OS requirements
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
"macos_updates": {
"minimum_version": "",
"deadline": ""
},
"ios_updates": {
"minimum_version": "",
"deadline": ""
},
"ipados_updates": {
"minimum_version": "",
"deadline": ""
}
}
}`), http.StatusOK, &acResp)
require.Empty(t, acResp.MDM.MacOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.MacOSUpdates.Deadline.Value)
require.Empty(t, acResp.MDM.IOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.IOSUpdates.Deadline.Value)
require.Empty(t, acResp.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.IPadOSUpdates.Deadline.Value)
// edited macos min version activity got created with empty requirement
lastActivity = s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"", "minimum_version":"", "team_id": null, "team_name": null}`, 0)
s.assertMacOSUpdatesDeclaration(nil, nil)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"", "minimum_version":"", "team_id": null, "team_name": null}`, 0)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"", "minimum_version":"", "team_id": null, "team_name": null}`, 0)
lastActivity = s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"", "minimum_version":"", "team_id": null, "team_name": null}`, 0)
// update again with empty macos requirement
// check DDM profiles were removed
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, nil)
// update again with empty apple OS requirements
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
"macos_updates": {
"minimum_version": "",
"deadline": ""
},
"ios_updates": {
"minimum_version": "",
"deadline": ""
},
"ipados_updates": {
"minimum_version": "",
"deadline": ""
}
}
}`), http.StatusOK, &acResp)
require.Empty(t, acResp.MDM.MacOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.MacOSUpdates.Deadline.Value)
require.Empty(t, acResp.MDM.IOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.IOSUpdates.Deadline.Value)
require.Empty(t, acResp.MDM.IPadOSUpdates.MinimumVersion.Value)
require.Empty(t, acResp.MDM.IPadOSUpdates.Deadline.Value)
// no activity got created
// no activity or DDM profiles were created
s.lastActivityMatches("", ``, lastActivity)
s.assertMacOSUpdatesDeclaration(nil, nil)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, nil)
s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, nil)
}
func (s *integrationEnterpriseTestSuite) TestSSOJITProvisioning() {
@@ -11054,9 +11410,8 @@ func (s *integrationEnterpriseTestSuite) TestPKGSoftwareReconciliation() {
}
func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
t := s.T()
t.Skip("disabled calendar callbacks to address bugs")
ctx := context.Background()
t := s.T()
t.Cleanup(func() {
calendar.ClearMockEvents()
calendar.ClearMockChannels()
@@ -11068,6 +11423,12 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
require.NoError(t, err)
})
origRecentUpdateDuration := commonCalendar.RecentCalendarUpdateDuration
commonCalendar.RecentCalendarUpdateDuration = 1 * time.Millisecond
t.Cleanup(func() {
commonCalendar.RecentCalendarUpdateDuration = origRecentUpdateDuration
})
team1, err := s.ds.NewTeam(ctx, &fleet.Team{
Name: "team1",
})
@@ -11312,8 +11673,9 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
time.Sleep(100 * time.Millisecond)
team1CalendarEvents, err = s.ds.ListCalendarEvents(ctx, &team1.ID)
require.NoError(t, err)
require.Len(t, team1CalendarEvents, 1)
if event.UUID != team1CalendarEvents[0].UUID {
// Event should be rescheduled on a future date/time
if len(team1CalendarEvents) == 1 && team1CalendarEvents[0].UUID == event.UUID &&
team1CalendarEvents[0].StartTime.After(event.StartTime) {
done <- struct{}{}
return
}
@@ -11463,7 +11825,20 @@ func (s *integrationEnterpriseTestSuite) TestCalendarCallback() {
},
), http.StatusOK, &distributedResp)
// Callback should still work, but only clear the callback channel. Event in DB will be deleted on the next cron run.
// We set a flag that event was updated recently. Callback shouldn't do anything since event was updated recently
_, err = distributedLock.AcquireLock(ctx, commonCalendar.RecentUpdateKeyPrefix+event.UUID, commonCalendar.RecentCalendarUpdateValue,
1000)
require.NoError(t, err)
_ = s.DoRawWithHeaders("POST", "/api/v1/fleet/calendar/webhook/"+eventRecreated.UUID, []byte(""), http.StatusOK,
map[string]string{
"X-Goog-Channel-Id": details.ChannelID,
"X-Goog-Resource-State": "exists",
})
assert.Equal(t, 1, calendar.MockChannelsCount())
// Callback should work, but only clear the callback channel. Event in DB will be deleted on the next cron run.
_, err = distributedLock.ReleaseLock(ctx, commonCalendar.RecentUpdateKeyPrefix+event.UUID, commonCalendar.RecentCalendarUpdateValue)
require.NoError(t, err)
_ = s.DoRawWithHeaders("POST", "/api/v1/fleet/calendar/webhook/"+eventRecreated.UUID, []byte(""), http.StatusOK,
map[string]string{
"X-Goog-Channel-Id": details.ChannelID,
@@ -364,7 +364,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() {
}`), http.StatusOK)
s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", tm.ID), fleet.TeamPayload{
MDM: &fleet.TeamPayloadMDM{
MacOSUpdates: &fleet.MacOSUpdates{
MacOSUpdates: &fleet.AppleOSUpdateSettings{
Deadline: optjson.SetString("1992-01-01"),
MinimumVersion: optjson.SetString("13.1.1"),
},
@@ -2865,28 +2865,33 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
{ProfileUUID: teamAppleProfUUID, Platform: "darwin", Name: "apple-team-profile", Identifier: "test-team-ident", TeamID: &testTeam.ID},
{ProfileUUID: noTeamWinProfUUID, Platform: "windows", Name: "win-global-profile", TeamID: nil},
{ProfileUUID: teamWinProfUUID, Platform: "windows", Name: "win-team-profile", TeamID: &testTeam.ID},
{ProfileUUID: uuidAppleDDMWithLabel, Platform: "darwin", Name: "apple-decl-with-labels", Identifier: "ident-decl-with-labels", TeamID: nil,
{
ProfileUUID: uuidAppleDDMWithLabel, Platform: "darwin", Name: "apple-decl-with-labels", Identifier: "ident-decl-with-labels", TeamID: nil,
LabelsIncludeAll: []fleet.ConfigurationProfileLabel{
{LabelID: labelFoo.ID, LabelName: labelFoo.Name},
},
},
{ProfileUUID: uuidAppleWithLabel, Platform: "darwin", Name: "apple-profile-with-labels", Identifier: "ident-with-labels", TeamID: nil,
{
ProfileUUID: uuidAppleWithLabel, Platform: "darwin", Name: "apple-profile-with-labels", Identifier: "ident-with-labels", TeamID: nil,
LabelsIncludeAll: []fleet.ConfigurationProfileLabel{
{LabelID: labelFoo.ID, LabelName: labelFoo.Name},
},
},
{ProfileUUID: uuidWindowsWithLabel, Platform: "windows", Name: "win-profile-with-labels", TeamID: nil,
{
ProfileUUID: uuidWindowsWithLabel, Platform: "windows", Name: "win-profile-with-labels", TeamID: nil,
LabelsExcludeAny: []fleet.ConfigurationProfileLabel{
{LabelID: labelBar.ID, LabelName: labelBar.Name},
{LabelID: labelFoo.ID, LabelName: labelFoo.Name},
},
},
{ProfileUUID: uuidAppleDDMTeamWithLabel, Platform: "darwin", Name: "apple-team-decl-with-labels", Identifier: "ident-team-decl-with-labels", TeamID: &testTeam.ID,
{
ProfileUUID: uuidAppleDDMTeamWithLabel, Platform: "darwin", Name: "apple-team-decl-with-labels", Identifier: "ident-team-decl-with-labels", TeamID: &testTeam.ID,
LabelsExcludeAny: []fleet.ConfigurationProfileLabel{
{LabelID: labelFoo.ID, LabelName: labelFoo.Name},
},
},
{ProfileUUID: uuidWindowsTeamWithLabel, Platform: "windows", Name: "win-team-profile-with-labels", TeamID: &testTeam.ID,
{
ProfileUUID: uuidWindowsTeamWithLabel, Platform: "windows", Name: "win-team-profile-with-labels", TeamID: &testTeam.ID,
LabelsIncludeAll: []fleet.ConfigurationProfileLabel{
{LabelID: labelBar.ID, LabelName: labelBar.Name},
{LabelID: labelFoo.ID, LabelName: labelFoo.Name},
@@ -4282,7 +4287,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() {
DeadlineDays: optjson.SetInt(4),
GracePeriodDays: optjson.SetInt(1),
},
MacOSUpdates: &fleet.MacOSUpdates{
MacOSUpdates: &fleet.AppleOSUpdateSettings{
Deadline: optjson.SetString("2023-12-31"),
MinimumVersion: optjson.SetString("13.3.8"),
},
+86 -30
View File
@@ -959,6 +959,43 @@ func createHostThenEnrollMDM(ds fleet.Datastore, fleetServerURL string, t *testi
return fleetHost, mdmDevice
}
func (s *integrationMDMTestSuite) createAppleMobileHostThenEnrollMDM(platform string) (*fleet.Host, *mdmtest.TestAppleMDMClient) {
ctx := context.Background()
t := s.T()
// create a host with minimal information and the serial, no uuid/osquery id
// (as when created via DEP sync).
dbZeroTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
serialNumber := mdmtest.RandSerialNumber()
fleetHost, err := s.ds.NewHost(ctx, &fleet.Host{
HardwareSerial: serialNumber,
Platform: platform,
LastEnrolledAt: dbZeroTime,
DetailUpdatedAt: dbZeroTime,
RefetchRequested: true,
})
require.NoError(t, err)
require.Equal(t, dbZeroTime, fleetHost.LastEnrolledAt)
// Perform the MDM enrollment.
mdmEnrollInfo := mdmtest.AppleEnrollInfo{
SCEPChallenge: s.scepChallenge,
SCEPURL: s.server.URL + apple_mdm.SCEPPath,
MDMURL: s.server.URL + apple_mdm.MDMPath,
}
model := "iPhone14,6"
if platform == "ipados" {
model = "iPad13,18"
}
mdmDevice := mdmtest.NewTestMDMClientAppleDirect(mdmEnrollInfo, model)
mdmDevice.SerialNumber = serialNumber
err = mdmDevice.Enroll()
require.NoError(t, err)
return fleetHost, mdmDevice
}
func createWindowsHostThenEnrollMDM(ds fleet.Datastore, fleetServerURL string, t *testing.T) (*fleet.Host, *mdmtest.TestWindowsMDMClient) {
host := createOrbitEnrolledHost(t, "windows", "h1", ds)
mdmDevice := mdmtest.NewTestMDMClientWindowsProgramatic(fleetServerURL, *host.OrbitNodeKey)
@@ -5610,7 +5647,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
resp = orbitGetConfigResponse{}
s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp)
wantCfg, err := fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("12.1.3")})
wantCfg, err := fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("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")
@@ -5638,7 +5675,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
var tmResp teamResponse
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
MDM: &fleet.TeamPayloadMDM{
MacOSUpdates: &fleet.MacOSUpdates{
MacOSUpdates: &fleet.AppleOSUpdateSettings{
Deadline: optjson.SetString("1992-01-01"),
MinimumVersion: optjson.SetString("13.1.1"),
},
@@ -5648,7 +5685,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
resp = orbitGetConfigResponse{}
s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp)
wantCfg, err = fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: optjson.SetString("1992-01-01"), MinimumVersion: optjson.SetString("13.1.1")})
wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("1992-01-01"), MinimumVersion: optjson.SetString("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")
@@ -5670,7 +5707,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
resp = orbitGetConfigResponse{}
s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h2.OrbitNodeKey)), http.StatusOK, &resp)
wantCfg, err = fleet.NewNudgeConfig(fleet.MacOSUpdates{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("12.1.3")})
wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("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")
@@ -9619,39 +9656,58 @@ func (s *integrationMDMTestSuite) TestInvalidCommandUUID() {
func (s *integrationMDMTestSuite) TestEnrollAfterDEPSyncIOSIPadOS() {
t := s.T()
ctx := context.Background()
// create a host with minimal information and the serial, no uuid/osquery id
// (as when created via DEP sync).
dbZeroTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
serialNumber := mdmtest.RandSerialNumber()
h, err := s.ds.NewHost(ctx, &fleet.Host{
HardwareSerial: serialNumber,
Platform: "ios",
LastEnrolledAt: dbZeroTime,
DetailUpdatedAt: dbZeroTime,
RefetchRequested: true,
})
require.NoError(t, err)
require.Equal(t, dbZeroTime, h.LastEnrolledAt)
// Perform the MDM enrollment.
mdmEnrollInfo := mdmtest.AppleEnrollInfo{
SCEPChallenge: s.scepChallenge,
SCEPURL: s.server.URL + apple_mdm.SCEPPath,
MDMURL: s.server.URL + apple_mdm.MDMPath,
}
mdmDevice := mdmtest.NewTestMDMClientAppleDirect(mdmEnrollInfo, "iPhone14,6")
mdmDevice.SerialNumber = serialNumber
err = mdmDevice.Enroll()
require.NoError(t, err)
h, _ := s.createAppleMobileHostThenEnrollMDM("ios")
// fetch the host, it will match the one created above
// (NOTE: cannot check the returned OrbitNodeKey, this field is not part of the response)
var hostResp getHostResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", h.ID), nil, http.StatusOK, &hostResp)
require.Equal(t, h.ID, hostResp.Host.ID)
require.NotEqual(t, dbZeroTime, hostResp.Host.LastEnrolledAt)
require.NotEqual(t, h.LastEnrolledAt, hostResp.Host.LastEnrolledAt)
h, _ = s.createAppleMobileHostThenEnrollMDM("ipados")
// fetch the host, it will match the one created above
// (NOTE: cannot check the returned OrbitNodeKey, this field is not part of the response)
hostResp = getHostResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", h.ID), nil, http.StatusOK, &hostResp)
require.Equal(t, h.ID, hostResp.Host.ID)
require.NotEqual(t, h.LastEnrolledAt, hostResp.Host.LastEnrolledAt)
}
func (s *integrationMDMTestSuite) TestRefetchIOSIPadOS() {
t := s.T()
// Try to refetch host that is not MDM enrolled
serialNumber := mdmtest.RandSerialNumber()
fleetHost, err := s.ds.NewHost(context.Background(), &fleet.Host{
HardwareSerial: serialNumber,
Platform: "ipados",
LastEnrolledAt: time.Now(),
DetailUpdatedAt: time.Now(),
RefetchRequested: true,
})
require.NoError(t, err)
r := s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/refetch", fleetHost.ID), nil, http.StatusUnprocessableEntity, "error",
"host is not enrolled in MDM")
assert.Contains(t, extractServerErrorText(r.Body), "Host does not have MDM turned on")
// Try to refetch an MDM enrolled host
host, mdmClient := s.createAppleMobileHostThenEnrollMDM("ios")
_ = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/refetch", host.ID), nil, http.StatusOK)
// Check the MDM command
cmd, err := mdmClient.Idle()
require.NoError(t, err)
require.NotNil(t, cmd)
assert.Equal(t, "DeviceInformation", cmd.Command.RequestType)
var hostResp getHostResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp)
assert.Equal(t, host.ID, hostResp.Host.ID)
assert.True(t, host.RefetchRequested)
}
func (s *integrationMDMTestSuite) TestVPPApps() {
+1 -1
View File
@@ -188,7 +188,7 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf
svc, err = eeservice.NewService(
svc,
ds,
kitlog.NewNopLogger(),
logger,
fleetConfig,
mailer,
c,
+3 -1
View File
@@ -318,7 +318,9 @@ func TestTranslateCPEToCVE(t *testing.T) {
{ID: "CVE-2023-42950", resolvedInVersion: "17.2"},
{ID: "CVE-2024-23273", resolvedInVersion: "17.4"},
},
excludedCVEs: []string{"CVE-2023-28205"},
excludedCVEs: []string{
"CVE-2023-28205", // This vulnerability is for Safari 16.4.0
},
continuesToUpdate: true,
},
"cpe:2.3:a:apple:safari:16.4.0:*:*:*:*:macos:*:*": {
@@ -199,6 +199,9 @@ func (s *CVE) updateYearFile(year int, cves []nvdapi.CVEItem) error {
// Convert new API 2.0 format to legacy feed format and create map of new CVE information.
newLegacyCVEs := make(map[string]*schema.NVDCVEFeedJSON10DefCVEItem)
for _, cve := range cves {
if cve.CVE.VulnStatus != nil && *cve.CVE.VulnStatus == "Rejected" {
continue
}
legacyCVE := convertAPI20CVEToLegacy(cve.CVE, s.logger)
newLegacyCVEs[legacyCVE.CVE.CVEDataMeta.ID] = legacyCVE
}
@@ -249,6 +252,9 @@ func (s *CVE) updateVulnCheckYearFile(year int, cves []VulnCheckCVE, modCount, a
// Convert new API 2.0 format to legacy feed format and create map of new CVE information.
newLegacyCVEs := make(map[string]*schema.NVDCVEFeedJSON10DefCVEItem)
for _, cve := range cves {
if cve.CVE.VulnStatus != nil && *cve.CVE.VulnStatus == "Rejected" {
continue
}
legacyCVE := convertAPI20CVEToLegacy(cve.CVE, s.logger)
updateWithVulnCheckConfigurations(legacyCVE, cve.VcConfigurations)
newLegacyCVEs[legacyCVE.CVE.CVEDataMeta.ID] = legacyCVE