Surface MDM devices where DEP assignment failed (#16973)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Added functionality to surface MDM devices where DEP assignment failed.
|
||||
@@ -643,6 +643,9 @@ func newWorkerIntegrationsSchedule(
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
schedule.WithJob("dep_cooldowns", func(ctx context.Context) error {
|
||||
return worker.ProcessDEPCooldowns(ctx, ds, logger)
|
||||
}),
|
||||
)
|
||||
|
||||
return s, nil
|
||||
|
||||
+99
-98
@@ -1,100 +1,101 @@
|
||||
{
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"device_status": "unlocked",
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"pending_action": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"labels": [],
|
||||
"packs": [],
|
||||
"policies": [
|
||||
{
|
||||
"id": 1,
|
||||
"query": "select 1 from osquery_info where start_time > 1;",
|
||||
"name": "query1",
|
||||
"platform": "",
|
||||
"description": "Some description",
|
||||
"author_email": "alice@example.com",
|
||||
"author_id": 1,
|
||||
"author_name": "Alice",
|
||||
"response": "passes",
|
||||
"resolution": "Some resolution",
|
||||
"team_id": 1,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"query": "select 1 from osquery_info where start_time > 1;",
|
||||
"name": "query2",
|
||||
"platform": "",
|
||||
"description": "",
|
||||
"author_email": "alice@example.com",
|
||||
"author_id": 1,
|
||||
"author_name": "Alice",
|
||||
"response": "fails",
|
||||
"team_id": null,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
}
|
||||
],
|
||||
"status": "offline",
|
||||
"display_text": "test_host",
|
||||
"display_name": "test_host"
|
||||
}
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"dep_profile_error": false,
|
||||
"device_status": "unlocked",
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"pending_action": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"labels": [],
|
||||
"packs": [],
|
||||
"policies": [
|
||||
{
|
||||
"id": 1,
|
||||
"query": "select 1 from osquery_info where start_time > 1;",
|
||||
"name": "query1",
|
||||
"platform": "",
|
||||
"description": "Some description",
|
||||
"author_email": "alice@example.com",
|
||||
"author_id": 1,
|
||||
"author_name": "Alice",
|
||||
"response": "passes",
|
||||
"resolution": "Some resolution",
|
||||
"team_id": 1,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"query": "select 1 from osquery_info where start_time > 1;",
|
||||
"name": "query2",
|
||||
"platform": "",
|
||||
"description": "",
|
||||
"author_email": "alice@example.com",
|
||||
"author_id": 1,
|
||||
"author_name": "Alice",
|
||||
"response": "fails",
|
||||
"team_id": null,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
}
|
||||
],
|
||||
"status": "offline",
|
||||
"display_text": "test_host",
|
||||
"display_name": "test_host"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ spec:
|
||||
last_restarted_at: "0001-01-01T00:00:00Z"
|
||||
logger_tls_period: 0
|
||||
mdm:
|
||||
dep_profile_error: false
|
||||
device_status: unlocked
|
||||
encryption_key_available: false
|
||||
enrollment_status: null
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"dep_profile_error": false,
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
@@ -113,6 +114,7 @@
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"dep_profile_error": false,
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
|
||||
+136
-134
@@ -1,136 +1,138 @@
|
||||
[
|
||||
{
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host",
|
||||
"display_name": "test_host",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"additional": {
|
||||
"query1": [
|
||||
{
|
||||
"col1": "val",
|
||||
"col2": 42
|
||||
}
|
||||
]
|
||||
},
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"status": "offline",
|
||||
"display_text": "test_host"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host2",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host2",
|
||||
"display_name": "test_host2",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"status": "offline",
|
||||
"display_text": "test_host2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host",
|
||||
"display_name": "test_host",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"dep_profile_error": false,
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"additional": {
|
||||
"query1": [
|
||||
{
|
||||
"col1": "val",
|
||||
"col2": 42
|
||||
}
|
||||
]
|
||||
},
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"status": "offline",
|
||||
"display_text": "test_host"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "host",
|
||||
"apiVersion": "v1",
|
||||
"spec": {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"id": 0,
|
||||
"detail_updated_at": "0001-01-01T00:00:00Z",
|
||||
"label_updated_at": "0001-01-01T00:00:00Z",
|
||||
"last_enrolled_at": "0001-01-01T00:00:00Z",
|
||||
"last_restarted_at": "0001-01-01T00:00:00Z",
|
||||
"seen_time": "0001-01-01T00:00:00Z",
|
||||
"software_updated_at": "0001-01-01T00:00:00Z",
|
||||
"refetch_requested": false,
|
||||
"refetch_critical_queries_until": null,
|
||||
"hostname": "test_host2",
|
||||
"uuid": "",
|
||||
"platform": "",
|
||||
"osquery_version": "",
|
||||
"os_version": "",
|
||||
"build": "",
|
||||
"platform_like": "",
|
||||
"policy_updated_at": "0001-01-01T00:00:00Z",
|
||||
"code_name": "",
|
||||
"uptime": 0,
|
||||
"memory": 0,
|
||||
"cpu_type": "",
|
||||
"cpu_subtype": "",
|
||||
"cpu_brand": "",
|
||||
"cpu_physical_cores": 0,
|
||||
"cpu_logical_cores": 0,
|
||||
"hardware_vendor": "",
|
||||
"hardware_model": "",
|
||||
"hardware_version": "",
|
||||
"hardware_serial": "",
|
||||
"computer_name": "test_host2",
|
||||
"display_name": "test_host2",
|
||||
"public_ip": "",
|
||||
"primary_ip": "",
|
||||
"primary_mac": "",
|
||||
"distributed_interval": 0,
|
||||
"config_tls_refresh": 0,
|
||||
"logger_tls_period": 0,
|
||||
"mdm": {
|
||||
"dep_profile_error": false,
|
||||
"encryption_key_available": false,
|
||||
"enrollment_status": null,
|
||||
"name": "",
|
||||
"server_url": null
|
||||
},
|
||||
"team_id": null,
|
||||
"pack_stats": null,
|
||||
"team_name": null,
|
||||
"gigs_disk_space_available": 0,
|
||||
"percent_disk_space_available": 0,
|
||||
"gigs_total_disk_space": 0,
|
||||
"issues": {
|
||||
"total_issues_count": 0,
|
||||
"failing_policies_count": 0
|
||||
},
|
||||
"status": "offline",
|
||||
"display_text": "test_host2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -36,6 +36,7 @@ spec:
|
||||
last_restarted_at: "0001-01-01T00:00:00Z"
|
||||
logger_tls_period: 0
|
||||
mdm:
|
||||
dep_profile_error: false
|
||||
encryption_key_available: false
|
||||
enrollment_status: null
|
||||
name: ""
|
||||
@@ -93,6 +94,7 @@ spec:
|
||||
last_restarted_at: "0001-01-01T00:00:00Z"
|
||||
logger_tls_period: 0
|
||||
mdm:
|
||||
dep_profile_error: false
|
||||
encryption_key_available: false
|
||||
enrollment_status: null
|
||||
server_url: null
|
||||
|
||||
@@ -258,7 +258,7 @@ func (svc *Service) updateAppConfigMDMAppleSetup(ctx context.Context, payload fl
|
||||
}
|
||||
|
||||
func (svc *Service) updateMacOSSetupEnableEndUserAuth(ctx context.Context, enable bool, teamID *uint, teamName *string) error {
|
||||
if err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger, worker.MacosSetupAssistantUpdateProfile, teamID); err != nil {
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger, worker.MacosSetupAssistantUpdateProfile, teamID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant update profile job")
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ func (svc *Service) SetOrUpdateMDMAppleSetupAssistant(ctx context.Context, asst
|
||||
|
||||
// if the name is the same and the content did not change, uploaded at will stay the same
|
||||
if prevAsst == nil || newAsst.Name != prevAsst.Name || newAsst.UploadedAt.After(prevAsst.UploadedAt) {
|
||||
if err := worker.QueueMacosSetupAssistantJob(
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(
|
||||
ctx,
|
||||
svc.ds,
|
||||
svc.logger,
|
||||
@@ -618,7 +618,7 @@ func (svc *Service) DeleteMDMAppleSetupAssistant(ctx context.Context, teamID *ui
|
||||
}
|
||||
|
||||
if prevAsst != nil {
|
||||
if err := worker.QueueMacosSetupAssistantJob(
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(
|
||||
ctx,
|
||||
svc.ds,
|
||||
svc.logger,
|
||||
@@ -802,7 +802,7 @@ func (svc *Service) mdmSSOHandleCallbackAuth(ctx context.Context, auth fleet.Aut
|
||||
}
|
||||
|
||||
func (svc *Service) mdmAppleSyncDEPProfiles(ctx context.Context) error {
|
||||
if err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger, worker.MacosSetupAssistantUpdateAllProfiles, nil); err != nil {
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger, worker.MacosSetupAssistantUpdateAllProfiles, nil); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant update all profiles job")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -519,6 +519,9 @@ func (svc *Service) DeleteTeam(ctx context.Context, teamID uint) error {
|
||||
mdmHostSerials := make([]string, 0, len(hosts))
|
||||
for _, host := range hosts {
|
||||
hostIDs = append(hostIDs, host.ID)
|
||||
// FIXME: These checks don't work here because host.MDMInfo is not being populated by
|
||||
// ds.ListHosts call (it populates host.MDM instead). This may be happening in other
|
||||
// places too.
|
||||
if host.MDMInfo.IsPendingDEPFleetEnrollment() || host.MDMInfo.IsDEPFleetEnrolled() {
|
||||
mdmHostSerials = append(mdmHostSerials, host.HardwareSerial)
|
||||
}
|
||||
@@ -538,7 +541,7 @@ func (svc *Service) DeleteTeam(ctx context.Context, teamID uint) error {
|
||||
}
|
||||
|
||||
if len(mdmHostSerials) > 0 {
|
||||
if err := worker.QueueMacosSetupAssistantJob(
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(
|
||||
ctx,
|
||||
svc.ds,
|
||||
svc.logger,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import Icon from "components/Icon";
|
||||
import NotSupported from "components/NotSupported";
|
||||
import { IHost } from "interfaces/host";
|
||||
|
||||
const baseClass = "host-mdm-status-cell";
|
||||
|
||||
const HostMdmStatusCell = ({
|
||||
row: {
|
||||
original: { id, mdm, platform },
|
||||
},
|
||||
cell: { value },
|
||||
}: {
|
||||
row: { original: IHost };
|
||||
cell: { value: string };
|
||||
}): JSX.Element => {
|
||||
if (platform === "chrome") {
|
||||
return NotSupported;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return <span className={`${baseClass}`}>{DEFAULT_EMPTY_CELL_VALUE}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`${baseClass}`}>
|
||||
{value}
|
||||
{mdm?.dep_profile_error && (
|
||||
<>
|
||||
<span
|
||||
className={`tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`host-mdm-status__${id}`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<Icon name="error-outline" color="status-error" size="medium" />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`host-mdm-status__${id}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
Fleet hit Apple’s API rate limit when preparing the macOS Setup
|
||||
Assistant for this host. Fleet will try again every hour.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostMdmStatusCell;
|
||||
@@ -0,0 +1,10 @@
|
||||
.host-mdm-status-cell {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
text-wrap: nowrap;
|
||||
gap: $pad-small;
|
||||
|
||||
.tooltip__tooltip-text {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./HostMdmStatusCell";
|
||||
@@ -163,6 +163,7 @@ export type HostMdmPendingAction = "unlock" | "lock" | "";
|
||||
export interface IHostMdmData {
|
||||
encryption_key_available: boolean;
|
||||
enrollment_status: MdmEnrollmentStatus | null;
|
||||
dep_profile_error?: boolean;
|
||||
name?: string;
|
||||
id?: number;
|
||||
server_url: string | null;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { IDeviceUser, IHost } from "interfaces/host";
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import DiskSpaceGraph from "components/DiskSpaceGraph";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import HostMdmStatusCell from "components/TableContainer/DataTable/HostMdmStatusCell/HostMdmStatusCell";
|
||||
import IssueCell from "components/TableContainer/DataTable/IssueCell/IssueCell";
|
||||
import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell";
|
||||
import StatusIndicator from "components/StatusIndicator";
|
||||
@@ -402,15 +403,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
disableSortBy: true,
|
||||
accessor: "mdm.enrollment_status",
|
||||
id: "mdm_enrollment_status",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
if (cellProps.row.original.platform === "chrome") {
|
||||
return NotSupported;
|
||||
}
|
||||
if (cellProps.cell.value) {
|
||||
return <TextCell value={cellProps.cell.value} />;
|
||||
}
|
||||
return <span className="text-muted">{DEFAULT_EMPTY_CELL_VALUE}</span>;
|
||||
},
|
||||
Cell: HostMdmStatusCell,
|
||||
},
|
||||
{
|
||||
title: "MDM server URL",
|
||||
|
||||
@@ -2852,6 +2852,189 @@ func (ds *Datastore) GetMDMAppleDefaultSetupAssistant(ctx context.Context, teamI
|
||||
return asst.ProfileUUID, asst.UploadedAt, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) UpdateHostDEPAssignProfileResponses(ctx context.Context, payload *godep.ProfileResponse) error {
|
||||
if payload == nil {
|
||||
// caller should ensure this does not happen
|
||||
level.Debug(ds.logger).Log("msg", "update host dep assign profiles responses received nil payload")
|
||||
return nil
|
||||
}
|
||||
|
||||
// we expect all devices to success so pre-allocate just the success slice
|
||||
success := make([]string, 0, len(payload.Devices))
|
||||
var (
|
||||
notAccessible []string
|
||||
failed []string
|
||||
)
|
||||
|
||||
for serial, status := range payload.Devices {
|
||||
switch status {
|
||||
case string(fleet.DEPAssignProfileResponseSuccess):
|
||||
success = append(success, serial)
|
||||
case string(fleet.DEPAssignProfileResponseNotAccessible):
|
||||
notAccessible = append(notAccessible, serial)
|
||||
case string(fleet.DEPAssignProfileResponseFailed):
|
||||
failed = append(failed, serial)
|
||||
default:
|
||||
// this should never happen unless Apple changes the response format, so we log it for
|
||||
// future debugging
|
||||
level.Debug(ds.logger).Log("msg", "unrecognized assign profile response", "serial", serial, "status", status)
|
||||
}
|
||||
}
|
||||
|
||||
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
if err := updateHostDEPAssignProfileResponses(ctx, tx, ds.logger, payload.ProfileUUID, success, string(fleet.DEPAssignProfileResponseSuccess)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateHostDEPAssignProfileResponses(ctx, tx, ds.logger, payload.ProfileUUID, notAccessible, string(fleet.DEPAssignProfileResponseNotAccessible)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateHostDEPAssignProfileResponses(ctx, tx, ds.logger, payload.ProfileUUID, failed, string(fleet.DEPAssignProfileResponseFailed)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func updateHostDEPAssignProfileResponses(ctx context.Context, tx sqlx.ExtContext, logger log.Logger, profileUUID string, serials []string, status string) error {
|
||||
if len(serials) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt := `
|
||||
UPDATE
|
||||
host_dep_assignments
|
||||
JOIN
|
||||
hosts ON id = host_id
|
||||
SET
|
||||
profile_uuid = ?,
|
||||
assign_profile_response = ?,
|
||||
response_updated_at = CURRENT_TIMESTAMP,
|
||||
retry_job_id = 0
|
||||
WHERE
|
||||
hardware_serial IN (?)
|
||||
`
|
||||
stmt, args, err := sqlx.In(stmt, profileUUID, status, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "prepare statement arguments")
|
||||
}
|
||||
res, err := tx.ExecContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "update host dep assignments")
|
||||
}
|
||||
|
||||
n, _ := res.RowsAffected()
|
||||
level.Info(logger).Log("msg", "update host dep assign profile responses", "profile_uuid", profileUUID, "status", status, "devices", n, "serials", fmt.Sprintf("%s", serials))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// depCooldownPeriod is the waiting period following a failed DEP assign profile request for a host.
|
||||
const depCooldownPeriod = 1 * time.Hour // TODO: Make this a test config option?
|
||||
|
||||
func (ds *Datastore) ScreenDEPAssignProfileSerialsForCooldown(ctx context.Context, serials []string) (skipSerials []string, assignSerials []string, err error) {
|
||||
stmt := `
|
||||
SELECT
|
||||
CASE WHEN assign_profile_response = ? AND (response_updated_at > DATE_SUB(NOW(), INTERVAL ? SECOND) OR retry_job_id != 0) THEN
|
||||
'skip'
|
||||
ELSE
|
||||
'assign'
|
||||
END AS status,
|
||||
hardware_serial
|
||||
FROM
|
||||
host_dep_assignments
|
||||
JOIN hosts ON id = host_id
|
||||
WHERE
|
||||
hardware_serial IN (?)
|
||||
`
|
||||
|
||||
stmt, args, err := sqlx.In(stmt, string(fleet.DEPAssignProfileResponseFailed), depCooldownPeriod.Seconds(), serials)
|
||||
if err != nil {
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "screen dep serials: prepare statement arguments")
|
||||
}
|
||||
|
||||
var rows []struct {
|
||||
Status string `db:"status"`
|
||||
HardwareSerial string `db:"hardware_serial"`
|
||||
}
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &rows, stmt, args...); err != nil {
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "screen dep serials: get rows")
|
||||
}
|
||||
|
||||
for _, r := range rows {
|
||||
switch r.Status {
|
||||
case "assign":
|
||||
assignSerials = append(assignSerials, r.HardwareSerial)
|
||||
case "skip":
|
||||
skipSerials = append(skipSerials, r.HardwareSerial)
|
||||
default:
|
||||
return nil, nil, ctxerr.New(ctx, fmt.Sprintf("screen dep serials: %s unrecognized status: %s", r.HardwareSerial, r.Status))
|
||||
}
|
||||
}
|
||||
|
||||
return skipSerials, assignSerials, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetDEPAssignProfileExpiredCooldowns(ctx context.Context) (map[uint][]string, error) {
|
||||
const stmt = `
|
||||
SELECT
|
||||
COALESCE(team_id, 0) AS team_id,
|
||||
hardware_serial
|
||||
FROM
|
||||
host_dep_assignments
|
||||
JOIN hosts h ON h.id = host_id
|
||||
LEFT JOIN jobs j ON j.id = retry_job_id
|
||||
WHERE
|
||||
assign_profile_response = ?
|
||||
AND(retry_job_id = 0 OR j.state = ?)
|
||||
AND(response_updated_at IS NULL
|
||||
OR response_updated_at <= DATE_SUB(NOW(), INTERVAL ? SECOND))`
|
||||
|
||||
var rows []struct {
|
||||
TeamID uint `db:"team_id"`
|
||||
HardwareSerial string `db:"hardware_serial"`
|
||||
}
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &rows, stmt, string(fleet.DEPAssignProfileResponseFailed), string(fleet.JobStateFailure), depCooldownPeriod.Seconds()); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host dep assign profile expired cooldowns")
|
||||
}
|
||||
|
||||
serialsByTeamID := make(map[uint][]string, len(rows))
|
||||
for _, r := range rows {
|
||||
serialsByTeamID[r.TeamID] = append(serialsByTeamID[r.TeamID], r.HardwareSerial)
|
||||
}
|
||||
return serialsByTeamID, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error {
|
||||
if len(serials) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt := `
|
||||
UPDATE
|
||||
host_dep_assignments
|
||||
JOIN
|
||||
hosts ON id = host_id
|
||||
SET
|
||||
retry_job_id = ?
|
||||
WHERE
|
||||
hardware_serial IN (?)`
|
||||
|
||||
stmt, args, err := sqlx.In(stmt, jobID, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "prepare statement arguments")
|
||||
}
|
||||
|
||||
res, err := ds.writer(ctx).ExecContext(ctx, stmt, args...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "update dep assign profile retry pending")
|
||||
}
|
||||
|
||||
n, _ := res.RowsAffected()
|
||||
level.Info(ds.logger).Log("msg", "update dep assign profile retry pending", "job_id", jobID, "devices", n, "serials", fmt.Sprintf("%s", serials))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) ResetMDMAppleEnrollment(ctx context.Context, hostUUID string) error {
|
||||
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
|
||||
// it's okay if we didn't update any rows, `nano_enrollments` entries
|
||||
|
||||
@@ -755,6 +755,11 @@ const hostMDMSelect = `,
|
||||
WHEN hmdm.enrolled = 0 AND hmdm.installed_from_dep = 0 THEN 'Off'
|
||||
ELSE NULL
|
||||
END,
|
||||
'dep_profile_error',
|
||||
CASE
|
||||
WHEN hdep.assign_profile_response = '` + string(fleet.DEPAssignProfileResponseFailed) + `' THEN CAST(TRUE AS JSON)
|
||||
ELSE CAST(FALSE AS JSON)
|
||||
END,
|
||||
'server_url',
|
||||
CASE
|
||||
WHEN hmdm.is_server = 1 THEN NULL
|
||||
@@ -798,6 +803,7 @@ const hostMDMJoin = `
|
||||
LEFT JOIN mobile_device_management_solutions mdms ON hm.mdm_id = mdms.id
|
||||
) hmdm ON hmdm.host_id = h.id
|
||||
LEFT JOIN host_disk_encryption_keys hdek ON hdek.host_id = h.id
|
||||
LEFT JOIN host_dep_assignments hdep ON hdep.host_id = h.id
|
||||
`
|
||||
|
||||
func amountEnrolledHostsByOSDB(ctx context.Context, db sqlx.QueryerContext) (byOS map[string][]fleet.HostsCountByOSVersion, totalCount int, err error) {
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20240228082706, Down_20240228082706)
|
||||
}
|
||||
|
||||
func Up_20240228082706(tx *sql.Tx) error {
|
||||
stmt := `
|
||||
ALTER TABLE host_dep_assignments
|
||||
-- profile_uuid is the uuid of the enrollment profile that was assigned to the host (which should correspond to an entry in the mdm_apple_setup_assistants table)
|
||||
ADD COLUMN profile_uuid VARCHAR(37) COLLATE utf8mb4_unicode_ci NULL,
|
||||
|
||||
-- assign_profile_response is the response received for the DEP profile assignment request (e.g., 'SUCCESS', 'NOT_ACCESSIBLE', or 'FAILED')
|
||||
ADD COLUMN assign_profile_response VARCHAR(15) COLLATE utf8mb4_unicode_ci NULL,
|
||||
|
||||
-- response_updated_at is the time the most recent DEP profile assignment response was received
|
||||
ADD COLUMN response_updated_at TIMESTAMP NULL,
|
||||
|
||||
-- retry_job_id is the id of job to retry a failed DEP profile assignment
|
||||
ADD COLUMN retry_job_id int(10) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
ADD INDEX idx_hdep_response (assign_profile_response, response_updated_at);`
|
||||
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
return errors.Wrap(err, "alter host_dep_assignments table")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20240228082706(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUp_20240228082706(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
insertStmt := "INSERT INTO host_dep_assignments (host_id) VALUES (?);"
|
||||
execNoErr(t, db, insertStmt, 1337)
|
||||
|
||||
// Apply current migration.
|
||||
applyNext(t, db)
|
||||
|
||||
// profile_uuid and assign_profile_response are now present and NULL
|
||||
type hda struct {
|
||||
HostID uint `db:"host_id"`
|
||||
AddedAt time.Time `db:"added_at"`
|
||||
DeletedAt *time.Time `db:"deleted_at"`
|
||||
ProfileUUID *string `db:"profile_uuid"`
|
||||
AssignProfileResponse *string `db:"assign_profile_response"`
|
||||
ResponseUpdatedAt *time.Time `db:"response_updated_at"`
|
||||
RetryJobID uint `db:"retry_job_id"`
|
||||
}
|
||||
var dest hda
|
||||
err := sqlx.Get(db, &dest, `SELECT host_id, added_at, deleted_at, profile_uuid, assign_profile_response, response_updated_at, retry_job_id FROM host_dep_assignments WHERE host_id = ?`, 1337)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint(1337), dest.HostID)
|
||||
require.NotZero(t, dest.AddedAt)
|
||||
require.Nil(t, dest.DeletedAt)
|
||||
require.Nil(t, dest.ProfileUUID)
|
||||
require.Nil(t, dest.AssignProfileResponse)
|
||||
require.Nil(t, dest.ResponseUpdatedAt)
|
||||
require.Zero(t, dest.RetryJobID)
|
||||
|
||||
// set profile_uuid and assign_profile_response to non-NULL values
|
||||
execNoErr(t, db, `UPDATE host_dep_assignments SET profile_uuid = 'foo', assign_profile_response = 'bar', response_updated_at = NOW() WHERE host_id = ?`, 1337)
|
||||
|
||||
dest = hda{}
|
||||
err = sqlx.Get(db, &dest, `SELECT host_id, added_at, deleted_at, profile_uuid, assign_profile_response, response_updated_at, retry_job_id FROM host_dep_assignments WHERE host_id = ?`, 1337)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint(1337), dest.HostID)
|
||||
require.NotZero(t, dest.AddedAt)
|
||||
require.Nil(t, dest.DeletedAt)
|
||||
require.Equal(t, "foo", *dest.ProfileUUID)
|
||||
require.Equal(t, "bar", *dest.AssignProfileResponse)
|
||||
require.NotNil(t, dest.ResponseUpdatedAt)
|
||||
require.NotZero(t, dest.ResponseUpdatedAt)
|
||||
require.Zero(t, dest.RetryJobID)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -442,6 +442,14 @@ func (h *HostDEPAssignment) IsDEPAssignedToFleet() bool {
|
||||
return h.HostID > 0 && !h.AddedAt.IsZero() && h.DeletedAt == nil
|
||||
}
|
||||
|
||||
type DEPAssignProfileResponseStatus string
|
||||
|
||||
const (
|
||||
DEPAssignProfileResponseSuccess DEPAssignProfileResponseStatus = "SUCCESS"
|
||||
DEPAssignProfileResponseNotAccessible DEPAssignProfileResponseStatus = "NOT_ACCESSIBLE"
|
||||
DEPAssignProfileResponseFailed DEPAssignProfileResponseStatus = "FAILED"
|
||||
)
|
||||
|
||||
// NanoEnrollment represents a row in the nano_enrollments table managed by
|
||||
// nanomdm. It is meant to be used internally by the server, not to be returned
|
||||
// as part of endpoints, and as a precaution its json-encoding is explicitly
|
||||
|
||||
@@ -1132,6 +1132,21 @@ type Datastore interface {
|
||||
// host_dep_assignments for host with matching serials.
|
||||
DeleteHostDEPAssignments(ctx context.Context, serials []string) error
|
||||
|
||||
// UpdateHostDEPAssignProfileResponses receives a profile UUID and threes lists of serials, each representing
|
||||
// one of the three possible responses, and updates the host_dep_assignments table with the corresponding responses.
|
||||
UpdateHostDEPAssignProfileResponses(ctx context.Context, resp *godep.ProfileResponse) error
|
||||
|
||||
// ScreenDEPAssignProfileSerialsForCooldown returns the serials that are still in cooldown and the
|
||||
// ones that are ready to be assigned a profile. If `screenRetryJobs` is true, it will also skip
|
||||
// any serials that have a non-zero `retry_job_id`.
|
||||
ScreenDEPAssignProfileSerialsForCooldown(ctx context.Context, serials []string) (skipSerials []string, assignSerials []string, err error)
|
||||
// GetDEPAssignProfileExpiredCooldowns returns the serials of the hosts that have expired
|
||||
// cooldowns, grouped by team.
|
||||
GetDEPAssignProfileExpiredCooldowns(ctx context.Context) (map[uint][]string, error)
|
||||
// UpdateDEPAssignProfileRetryPending sets the retry_pending flag for the hosts with the given
|
||||
// serials.
|
||||
UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Microsoft MDM
|
||||
|
||||
|
||||
@@ -364,6 +364,10 @@ type MDMHostData struct {
|
||||
// EnrollmentStatus is a string representation of state derived from
|
||||
// booleans stored in the host_mdm table, loaded by JOIN in datastore
|
||||
EnrollmentStatus *string `json:"enrollment_status" db:"-" csv:"mdm.enrollment_status"`
|
||||
// DEPProfileError is a boolean representing whether Fleet received a "FAILED" response when
|
||||
// attempting to assign a DEP profile for the host.
|
||||
// See https://developer.apple.com/documentation/devicemanagement/assignprofileresponse
|
||||
DEPProfileError bool `json:"dep_profile_error" db:"dep_profile_error" csv:"mdm.dep_profile_error"`
|
||||
// ServerURL is the server_url stored in the host_mdm table, loaded by
|
||||
// JOIN in datastore
|
||||
ServerURL *string `json:"server_url" db:"-" csv:"mdm.server_url"`
|
||||
|
||||
@@ -516,11 +516,26 @@ func (d *DEPService) processDeviceResponse(ctx context.Context, depClient *godep
|
||||
for profUUID, serials := range profileToSerials {
|
||||
logger := kitlog.With(d.logger, "profile_uuid", profUUID)
|
||||
level.Info(logger).Log("msg", "calling DEP client to assign profile", "profile_uuid", profUUID)
|
||||
apiResp, err := depClient.AssignProfile(ctx, DEPName, profUUID, serials...)
|
||||
|
||||
skipSerials, assignSerials, err := d.ds.ScreenDEPAssignProfileSerialsForCooldown(ctx, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "process device response")
|
||||
}
|
||||
if len(skipSerials) > 0 {
|
||||
// NOTE: the `dep_cooldown` job of the `integrations`` cron picks up the assignments
|
||||
// after the cooldown period is over
|
||||
level.Debug(logger).Log("msg", "process device response: skipping assign profile for devices on cooldown", "serials", fmt.Sprintf("%s", skipSerials))
|
||||
}
|
||||
if len(assignSerials) == 0 {
|
||||
level.Debug(logger).Log("msg", "process device response: no devices to assign profile")
|
||||
continue
|
||||
}
|
||||
|
||||
apiResp, err := depClient.AssignProfile(ctx, DEPName, profUUID, assignSerials...)
|
||||
if err != nil {
|
||||
level.Info(logger).Log(
|
||||
"msg", "assign profile",
|
||||
"devices", len(serials),
|
||||
"devices", len(assignSerials),
|
||||
"err", err,
|
||||
)
|
||||
return fmt.Errorf("assign profile: %w", err)
|
||||
@@ -528,16 +543,14 @@ func (d *DEPService) processDeviceResponse(ctx context.Context, depClient *godep
|
||||
|
||||
logs := []interface{}{
|
||||
"msg", "profile assigned",
|
||||
"devices", len(serials),
|
||||
"devices", len(assignSerials),
|
||||
}
|
||||
logs = append(logs, logCountsForResults(apiResp.Devices)...)
|
||||
level.Info(logger).Log(logs...)
|
||||
|
||||
debugLogs := []interface{}{"msg", "assign profile responses by device"}
|
||||
for k, v := range apiResp.Devices {
|
||||
debugLogs = append(debugLogs, k, v)
|
||||
if err := d.ds.UpdateHostDEPAssignProfileResponses(ctx, apiResp); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "update host dep assign profile responses")
|
||||
}
|
||||
level.Debug(logger).Log(debugLogs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -426,8 +426,6 @@ type NewGlobalPolicyFunc func(ctx context.Context, authorID *uint, args fleet.Po
|
||||
|
||||
type PolicyFunc func(ctx context.Context, id uint) (*fleet.Policy, error)
|
||||
|
||||
type PolicyByNameFunc func(ctx context.Context, name string) (*fleet.Policy, error)
|
||||
|
||||
type SavePolicyFunc func(ctx context.Context, p *fleet.Policy, shouldRemoveAllPolicyMemberships bool) error
|
||||
|
||||
type ListGlobalPoliciesFunc func(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error)
|
||||
@@ -748,6 +746,14 @@ type GetMatchingHostSerialsFunc func(ctx context.Context, serials []string) (map
|
||||
|
||||
type DeleteHostDEPAssignmentsFunc func(ctx context.Context, serials []string) error
|
||||
|
||||
type UpdateHostDEPAssignProfileResponsesFunc func(ctx context.Context, resp *godep.ProfileResponse) error
|
||||
|
||||
type ScreenDEPAssignProfileSerialsForCooldownFunc func(ctx context.Context, serials []string) (skipSerials []string, assignSerials []string, err error)
|
||||
|
||||
type GetDEPAssignProfileExpiredCooldownsFunc func(ctx context.Context) (map[uint][]string, error)
|
||||
|
||||
type UpdateDEPAssignProfileRetryPendingFunc func(ctx context.Context, jobID uint, serials []string) error
|
||||
|
||||
type WSTEPStoreCertificateFunc func(ctx context.Context, name string, crt *x509.Certificate) error
|
||||
|
||||
type WSTEPNewSerialFunc func(ctx context.Context) (*big.Int, error)
|
||||
@@ -1453,9 +1459,6 @@ type DataStore struct {
|
||||
PolicyFunc PolicyFunc
|
||||
PolicyFuncInvoked bool
|
||||
|
||||
PolicyByNameFunc PolicyByNameFunc
|
||||
PolicyByNameFuncInvoked bool
|
||||
|
||||
SavePolicyFunc SavePolicyFunc
|
||||
SavePolicyFuncInvoked bool
|
||||
|
||||
@@ -1936,6 +1939,18 @@ type DataStore struct {
|
||||
DeleteHostDEPAssignmentsFunc DeleteHostDEPAssignmentsFunc
|
||||
DeleteHostDEPAssignmentsFuncInvoked bool
|
||||
|
||||
UpdateHostDEPAssignProfileResponsesFunc UpdateHostDEPAssignProfileResponsesFunc
|
||||
UpdateHostDEPAssignProfileResponsesFuncInvoked bool
|
||||
|
||||
ScreenDEPAssignProfileSerialsForCooldownFunc ScreenDEPAssignProfileSerialsForCooldownFunc
|
||||
ScreenDEPAssignProfileSerialsForCooldownFuncInvoked bool
|
||||
|
||||
GetDEPAssignProfileExpiredCooldownsFunc GetDEPAssignProfileExpiredCooldownsFunc
|
||||
GetDEPAssignProfileExpiredCooldownsFuncInvoked bool
|
||||
|
||||
UpdateDEPAssignProfileRetryPendingFunc UpdateDEPAssignProfileRetryPendingFunc
|
||||
UpdateDEPAssignProfileRetryPendingFuncInvoked bool
|
||||
|
||||
WSTEPStoreCertificateFunc WSTEPStoreCertificateFunc
|
||||
WSTEPStoreCertificateFuncInvoked bool
|
||||
|
||||
@@ -3505,13 +3520,6 @@ func (s *DataStore) Policy(ctx context.Context, id uint) (*fleet.Policy, error)
|
||||
return s.PolicyFunc(ctx, id)
|
||||
}
|
||||
|
||||
func (s *DataStore) PolicyByName(ctx context.Context, name string) (*fleet.Policy, error) {
|
||||
s.mu.Lock()
|
||||
s.PolicyByNameFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.PolicyByNameFunc(ctx, name)
|
||||
}
|
||||
|
||||
func (s *DataStore) SavePolicy(ctx context.Context, p *fleet.Policy, shouldRemoveAllPolicyMemberships bool) error {
|
||||
s.mu.Lock()
|
||||
s.SavePolicyFuncInvoked = true
|
||||
@@ -4632,6 +4640,34 @@ func (s *DataStore) DeleteHostDEPAssignments(ctx context.Context, serials []stri
|
||||
return s.DeleteHostDEPAssignmentsFunc(ctx, serials)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpdateHostDEPAssignProfileResponses(ctx context.Context, resp *godep.ProfileResponse) error {
|
||||
s.mu.Lock()
|
||||
s.UpdateHostDEPAssignProfileResponsesFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.UpdateHostDEPAssignProfileResponsesFunc(ctx, resp)
|
||||
}
|
||||
|
||||
func (s *DataStore) ScreenDEPAssignProfileSerialsForCooldown(ctx context.Context, serials []string) (skipSerials []string, assignSerials []string, err error) {
|
||||
s.mu.Lock()
|
||||
s.ScreenDEPAssignProfileSerialsForCooldownFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.ScreenDEPAssignProfileSerialsForCooldownFunc(ctx, serials)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetDEPAssignProfileExpiredCooldowns(ctx context.Context) (map[uint][]string, error) {
|
||||
s.mu.Lock()
|
||||
s.GetDEPAssignProfileExpiredCooldownsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetDEPAssignProfileExpiredCooldownsFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error {
|
||||
s.mu.Lock()
|
||||
s.UpdateDEPAssignProfileRetryPendingFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.UpdateDEPAssignProfileRetryPendingFunc(ctx, jobID, serials)
|
||||
}
|
||||
|
||||
func (s *DataStore) WSTEPStoreCertificate(ctx context.Context, name string, crt *x509.Certificate) error {
|
||||
s.mu.Lock()
|
||||
s.WSTEPStoreCertificateFuncInvoked = true
|
||||
|
||||
@@ -2787,7 +2787,7 @@ func (svc *Service) restorePendingDEPHost(ctx context.Context, host *fleet.Host,
|
||||
return ctxerr.Wrap(ctx, err, "restore pending dep host")
|
||||
}
|
||||
|
||||
if err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger,
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(ctx, svc.ds, svc.logger,
|
||||
worker.MacosSetupAssistantHostsTransferred, tmID, host.HardwareSerial); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "restore pending dep host")
|
||||
}
|
||||
|
||||
@@ -775,7 +775,7 @@ func (svc *Service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []
|
||||
return ctxerr.Wrap(ctx, err, "list mdm dep serials in host ids")
|
||||
}
|
||||
if len(serials) > 0 {
|
||||
if err := worker.QueueMacosSetupAssistantJob(
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(
|
||||
ctx,
|
||||
svc.ds,
|
||||
svc.logger,
|
||||
@@ -911,7 +911,7 @@ func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, op
|
||||
return ctxerr.Wrap(ctx, err, "list mdm dep serials in host ids")
|
||||
}
|
||||
if len(serials) > 0 {
|
||||
if err := worker.QueueMacosSetupAssistantJob(
|
||||
if _, err := worker.QueueMacosSetupAssistantJob(
|
||||
ctx,
|
||||
svc.ds,
|
||||
svc.logger,
|
||||
|
||||
@@ -7047,7 +7047,7 @@ func (s *integrationTestSuite) TestHostsReportDownload() {
|
||||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows[0], 50) // total number of cols
|
||||
assert.Len(t, rows[0], 51) // total number of cols
|
||||
|
||||
const (
|
||||
idCol = 3
|
||||
@@ -9149,7 +9149,7 @@ func (s *integrationTestSuite) TestHostsReportWithPolicyResults() {
|
||||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows1, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows1[0], 50) // total number of cols
|
||||
assert.Len(t, rows1[0], 51) // total number of cols
|
||||
|
||||
var (
|
||||
idIdx int
|
||||
@@ -9176,7 +9176,7 @@ func (s *integrationTestSuite) TestHostsReportWithPolicyResults() {
|
||||
res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rows2, len(hosts)+1) // all hosts + header row
|
||||
assert.Len(t, rows2[0], 50) // total number of cols
|
||||
assert.Len(t, rows2[0], 51) // total number of cols
|
||||
|
||||
// Check that all hosts have 0 issues and that they match the previous call to `/hosts/report`.
|
||||
for i := 1; i < len(hosts)+1; i++ {
|
||||
|
||||
@@ -75,17 +75,19 @@ func TestIntegrationsMDM(t *testing.T) {
|
||||
type integrationMDMTestSuite struct {
|
||||
suite.Suite
|
||||
withServer
|
||||
fleetCfg config.FleetConfig
|
||||
fleetDMNextCSRStatus atomic.Value
|
||||
pushProvider *mock.APNSPushProvider
|
||||
depStorage nanodep_storage.AllDEPStorage
|
||||
depSchedule *schedule.Schedule
|
||||
profileSchedule *schedule.Schedule
|
||||
onProfileJobDone func() // function called when profileSchedule.Trigger() job completed
|
||||
onDEPScheduleDone func() // function called when depSchedule.Trigger() job completed
|
||||
mdmStorage *mysql.NanoMDMStorage
|
||||
worker *worker.Worker
|
||||
mdmCommander *apple_mdm.MDMAppleCommander
|
||||
fleetCfg config.FleetConfig
|
||||
fleetDMNextCSRStatus atomic.Value
|
||||
pushProvider *mock.APNSPushProvider
|
||||
depStorage nanodep_storage.AllDEPStorage
|
||||
depSchedule *schedule.Schedule
|
||||
profileSchedule *schedule.Schedule
|
||||
integrationsSchedule *schedule.Schedule
|
||||
onProfileJobDone func() // function called when profileSchedule.Trigger() job completed
|
||||
onDEPScheduleDone func() // function called when depSchedule.Trigger() job completed
|
||||
onIntegrationsScheduleDone func() // function called when integrationsSchedule.Trigger() job completed
|
||||
mdmStorage *mysql.NanoMDMStorage
|
||||
worker *worker.Worker
|
||||
mdmCommander *apple_mdm.MDMAppleCommander
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) SetupSuite() {
|
||||
@@ -126,7 +128,24 @@ func (s *integrationMDMTestSuite) SetupSuite() {
|
||||
redisPool := redistest.SetupRedis(s.T(), "zz", false, false, false)
|
||||
s.withServer.lq = live_query_mock.New(s.T())
|
||||
|
||||
macosJob := &worker.MacosSetupAssistant{
|
||||
Datastore: s.ds,
|
||||
Log: kitlog.NewJSONLogger(os.Stdout),
|
||||
DEPService: apple_mdm.NewDEPService(s.ds, depStorage, kitlog.NewJSONLogger(os.Stdout)),
|
||||
DEPClient: apple_mdm.NewDEPClient(depStorage, s.ds, kitlog.NewJSONLogger(os.Stdout)),
|
||||
}
|
||||
appleMDMJob := &worker.AppleMDM{
|
||||
Datastore: s.ds,
|
||||
Log: kitlog.NewJSONLogger(os.Stdout),
|
||||
Commander: mdmCommander,
|
||||
}
|
||||
workr := worker.NewWorker(s.ds, kitlog.NewJSONLogger(os.Stdout))
|
||||
workr.TestIgnoreUnknownJobs = true
|
||||
workr.Register(macosJob, appleMDMJob)
|
||||
s.worker = workr
|
||||
|
||||
var depSchedule *schedule.Schedule
|
||||
var integrationsSchedule *schedule.Schedule
|
||||
var profileSchedule *schedule.Schedule
|
||||
config := TestServerOpts{
|
||||
License: &fleet.LicenseInfo{
|
||||
@@ -187,6 +206,27 @@ func (s *integrationMDMTestSuite) SetupSuite() {
|
||||
return profileSchedule, nil
|
||||
}
|
||||
},
|
||||
func(ctx context.Context, ds fleet.Datastore) fleet.NewCronScheduleFunc {
|
||||
return func() (fleet.CronSchedule, error) {
|
||||
const name = string(fleet.CronWorkerIntegrations)
|
||||
logger := kitlog.NewJSONLogger(os.Stdout)
|
||||
integrationsSchedule = schedule.New(
|
||||
ctx, name, s.T().Name(), 1*time.Minute, ds, ds,
|
||||
schedule.WithLogger(logger),
|
||||
schedule.WithJob("integrations_worker", func(ctx context.Context) error {
|
||||
return s.worker.ProcessJobs(ctx)
|
||||
}),
|
||||
schedule.WithJob("dep_cooldowns", func(ctx context.Context) error {
|
||||
if s.onIntegrationsScheduleDone != nil {
|
||||
defer s.onIntegrationsScheduleDone()
|
||||
}
|
||||
|
||||
return worker.ProcessDEPCooldowns(ctx, ds, logger)
|
||||
}),
|
||||
)
|
||||
return integrationsSchedule, nil
|
||||
}
|
||||
},
|
||||
},
|
||||
APNSTopic: "com.apple.mgmt.External.10ac3ce5-4668-4e58-b69a-b2b5ce667589",
|
||||
}
|
||||
@@ -199,26 +239,11 @@ func (s *integrationMDMTestSuite) SetupSuite() {
|
||||
s.pushProvider = pushProvider
|
||||
s.depStorage = depStorage
|
||||
s.depSchedule = depSchedule
|
||||
s.integrationsSchedule = integrationsSchedule
|
||||
s.profileSchedule = profileSchedule
|
||||
s.mdmStorage = mdmStorage
|
||||
s.mdmCommander = mdmCommander
|
||||
|
||||
macosJob := &worker.MacosSetupAssistant{
|
||||
Datastore: s.ds,
|
||||
Log: kitlog.NewJSONLogger(os.Stdout),
|
||||
DEPService: apple_mdm.NewDEPService(s.ds, depStorage, kitlog.NewJSONLogger(os.Stdout)),
|
||||
DEPClient: apple_mdm.NewDEPClient(depStorage, s.ds, kitlog.NewJSONLogger(os.Stdout)),
|
||||
}
|
||||
appleMDMJob := &worker.AppleMDM{
|
||||
Datastore: s.ds,
|
||||
Log: kitlog.NewJSONLogger(os.Stdout),
|
||||
Commander: mdmCommander,
|
||||
}
|
||||
workr := worker.NewWorker(s.ds, kitlog.NewJSONLogger(os.Stdout))
|
||||
workr.TestIgnoreUnknownJobs = true
|
||||
workr.Register(macosJob, appleMDMJob)
|
||||
s.worker = workr
|
||||
|
||||
fleetdmSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
status := s.fleetDMNextCSRStatus.Swap(http.StatusOK)
|
||||
w.WriteHeader(status.(int))
|
||||
@@ -278,12 +303,19 @@ func (s *integrationMDMTestSuite) TearDownTest() {
|
||||
_, err := q.ExecContext(ctx, "DELETE FROM mdm_windows_configuration_profiles")
|
||||
return err
|
||||
})
|
||||
|
||||
// clear any pending worker job
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "DELETE FROM jobs")
|
||||
return err
|
||||
})
|
||||
|
||||
// clear any host dep assignments
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "DELETE FROM host_dep_assignments")
|
||||
return err
|
||||
})
|
||||
|
||||
// clear any mdm windows enrollments
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, "DELETE FROM mdm_windows_enrollments")
|
||||
@@ -1973,6 +2005,104 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
}
|
||||
}
|
||||
|
||||
checkAssignProfileRequests := func(serial string, profUUID *string) {
|
||||
require.NotEmpty(t, profileAssignmentReqs)
|
||||
require.Len(t, profileAssignmentReqs, 1)
|
||||
require.Len(t, profileAssignmentReqs[0].Devices, 1)
|
||||
require.Equal(t, serial, profileAssignmentReqs[0].Devices[0])
|
||||
if profUUID != nil {
|
||||
require.Equal(t, *profUUID, profileAssignmentReqs[0].ProfileUUID)
|
||||
}
|
||||
}
|
||||
|
||||
type hostDEPRow struct {
|
||||
HostID uint `db:"host_id"`
|
||||
ProfileUUID string `db:"profile_uuid"`
|
||||
AssignProfileResponse string `db:"assign_profile_response"`
|
||||
ResponseUpdatedAt time.Time `db:"response_updated_at"`
|
||||
RetryJobID uint `db:"retry_job_id"`
|
||||
}
|
||||
checkHostDEPAssignProfileResponses := func(deviceSerials []string, expectedProfileUUID string, expectedStatus fleet.DEPAssignProfileResponseStatus) map[string]hostDEPRow {
|
||||
bySerial := make(map[string]hostDEPRow, len(deviceSerials))
|
||||
for _, deviceSerial := range deviceSerials {
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
var dest hostDEPRow
|
||||
err := sqlx.GetContext(ctx, q, &dest, "SELECT host_id, assign_profile_response, profile_uuid, response_updated_at, retry_job_id FROM host_dep_assignments WHERE profile_uuid = ? AND host_id = (SELECT id FROM hosts WHERE hardware_serial = ?)", expectedProfileUUID, deviceSerial)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(expectedStatus), dest.AssignProfileResponse)
|
||||
bySerial[deviceSerial] = dest
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return bySerial
|
||||
}
|
||||
|
||||
checkPendingMacOSSetupAssistantJob := func(expectedTask string, expectedTeamID *uint, expectedSerials []string, expectedJobID uint) {
|
||||
pending, err := s.ds.GetQueuedJobs(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pending, 1)
|
||||
require.Equal(t, "macos_setup_assistant", pending[0].Name)
|
||||
require.NotNil(t, pending[0].Args)
|
||||
var gotArgs struct {
|
||||
Task string `json:"task"`
|
||||
TeamID *uint `json:"team_id,omitempty"`
|
||||
HostSerialNumbers []string `json:"host_serial_numbers,omitempty"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(*pending[0].Args, &gotArgs))
|
||||
require.Equal(t, expectedTask, gotArgs.Task)
|
||||
if expectedTeamID != nil {
|
||||
require.NotNil(t, gotArgs.TeamID)
|
||||
require.Equal(t, *expectedTeamID, *gotArgs.TeamID)
|
||||
} else {
|
||||
require.Nil(t, gotArgs.TeamID)
|
||||
}
|
||||
require.Equal(t, expectedSerials, gotArgs.HostSerialNumbers)
|
||||
|
||||
if expectedJobID != 0 {
|
||||
require.Equal(t, expectedJobID, pending[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
checkNoJobsPending := func() {
|
||||
pending, err := s.ds.GetQueuedJobs(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, pending)
|
||||
}
|
||||
|
||||
expectNoJobID := ptr.Uint(0) // used when expect no retry job
|
||||
checkHostCooldown := func(serial, profUUID string, status fleet.DEPAssignProfileResponseStatus, expectUpdatedAt *time.Time, expectRetryJobID *uint) hostDEPRow {
|
||||
bySerial := checkHostDEPAssignProfileResponses([]string{serial}, profUUID, status)
|
||||
d, ok := bySerial[serial]
|
||||
require.True(t, ok)
|
||||
if expectUpdatedAt != nil {
|
||||
require.Equal(t, *expectUpdatedAt, d.ResponseUpdatedAt)
|
||||
}
|
||||
if expectRetryJobID != nil {
|
||||
require.Equal(t, *expectRetryJobID, d.RetryJobID)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
checkListHostDEPError := func(serial string, expectStatus string, expectError bool) *fleet.HostResponse {
|
||||
listHostsRes := listHostsResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts?query=%s", serial), nil, http.StatusOK, &listHostsRes)
|
||||
require.Len(t, listHostsRes.Hosts, 1)
|
||||
require.Equal(t, serial, listHostsRes.Hosts[0].HardwareSerial)
|
||||
require.Equal(t, expectStatus, *listHostsRes.Hosts[0].MDM.EnrollmentStatus)
|
||||
require.Equal(t, expectError, listHostsRes.Hosts[0].MDM.DEPProfileError)
|
||||
|
||||
return &listHostsRes.Hosts[0]
|
||||
}
|
||||
|
||||
setAssignProfileResponseUpdatedAt := func(serial string, updatedAt time.Time) {
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, `UPDATE host_dep_assignments SET response_updated_at = ? WHERE host_id = (SELECT id FROM hosts WHERE hardware_serial = ?)`, updatedAt, serial)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
expectAssignProfileResponseFailed := "" // set to device serial when testing the failed profile assignment flow
|
||||
expectAssignProfileResponseNotAccessible := "" // set to device serial when testing the not accessible profile assignment flow
|
||||
s.mockDEPResponse(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
encoder := json.NewEncoder(w)
|
||||
@@ -1999,7 +2129,21 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
var prof profileAssignmentReq
|
||||
require.NoError(t, json.Unmarshal(b, &prof))
|
||||
profileAssignmentReqs = append(profileAssignmentReqs, prof)
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
var resp godep.ProfileResponse
|
||||
resp.ProfileUUID = prof.ProfileUUID
|
||||
resp.Devices = make(map[string]string, len(prof.Devices))
|
||||
for _, device := range prof.Devices {
|
||||
switch device {
|
||||
case expectAssignProfileResponseNotAccessible:
|
||||
resp.Devices[device] = string(fleet.DEPAssignProfileResponseNotAccessible)
|
||||
case expectAssignProfileResponseFailed:
|
||||
resp.Devices[device] = string(fleet.DEPAssignProfileResponseFailed)
|
||||
default:
|
||||
resp.Devices[device] = string(fleet.DEPAssignProfileResponseSuccess)
|
||||
}
|
||||
}
|
||||
err = encoder.Encode(resp)
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
}
|
||||
@@ -2032,7 +2176,9 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
// - one when we do the device sync (/device/sync)
|
||||
require.Len(t, profileAssignmentReqs, 2)
|
||||
require.Len(t, profileAssignmentReqs[0].Devices, 1)
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
require.Len(t, profileAssignmentReqs[1].Devices, len(devices))
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[1].Devices, profileAssignmentReqs[1].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// create a new host
|
||||
nonDEPHost := createHostAndDeviceToken(t, s.ds, "not-dep")
|
||||
@@ -2156,6 +2302,7 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
// TODO: seems like we're doing this request on each loop?
|
||||
require.Len(t, profileAssignmentReqs[0].Devices, 1)
|
||||
require.Equal(t, devices[0].SerialNumber, profileAssignmentReqs[0].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// profileAssignmentReqs[1] and [2] can be in any order
|
||||
ix2Devices, ix1Device := 1, 2
|
||||
@@ -2166,12 +2313,14 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
// - existing device with "added"
|
||||
// - new device with "added"
|
||||
require.Len(t, profileAssignmentReqs[ix2Devices].Devices, 2, "%#+v", profileAssignmentReqs)
|
||||
require.Equal(t, devices[0].SerialNumber, profileAssignmentReqs[ix2Devices].Devices[0])
|
||||
require.Equal(t, devices[0].SerialNumber, profileAssignmentReqs[ix2Devices].Devices[0]) // FIXME: this test seems to be flaky
|
||||
require.Equal(t, addedSerial, profileAssignmentReqs[ix2Devices].Devices[1])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[ix2Devices].Devices, profileAssignmentReqs[ix2Devices].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// - existing device with "modified" and a different team (thus different profile request)
|
||||
require.Len(t, profileAssignmentReqs[ix1Device].Devices, 1)
|
||||
require.Equal(t, devices[1].SerialNumber, profileAssignmentReqs[ix1Device].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[ix1Device].Devices, profileAssignmentReqs[ix1Device].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// entries for all hosts except for the one with OpType = "deleted"
|
||||
assignment, err := s.ds.GetHostDEPAssignment(ctx, deletedHostID)
|
||||
@@ -2205,6 +2354,7 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runWorker()
|
||||
require.Equal(t, mdmDevice.SerialNumber, profileAssignmentReqs[0].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// it should get the post-enrollment commands
|
||||
require.NoError(t, mdmDevice.Enroll())
|
||||
@@ -2272,6 +2422,11 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
s.runDEPSchedule()
|
||||
require.NotEmpty(t, profileAssignmentReqs)
|
||||
require.Equal(t, eHost.HardwareSerial, profileAssignmentReqs[0].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
|
||||
// report MDM info via osquery
|
||||
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, eHost.ID, false, true, s.server.URL, true, fleet.WellKnownMDMFleet, ""))
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", false)
|
||||
|
||||
// transfer to "no team", we assign a DEP profile to the device
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
@@ -2280,6 +2435,8 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
s.runWorker()
|
||||
require.NotEmpty(t, profileAssignmentReqs)
|
||||
require.Equal(t, eHost.HardwareSerial, profileAssignmentReqs[0].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", false)
|
||||
|
||||
// transfer to the team back again, we assign a DEP profile to the device again
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
@@ -2288,6 +2445,220 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
|
||||
s.runWorker()
|
||||
require.NotEmpty(t, profileAssignmentReqs)
|
||||
require.Equal(t, eHost.HardwareSerial, profileAssignmentReqs[0].Devices[0])
|
||||
checkHostDEPAssignProfileResponses(profileAssignmentReqs[0].Devices, profileAssignmentReqs[0].ProfileUUID, fleet.DEPAssignProfileResponseSuccess)
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", false)
|
||||
|
||||
// transfer to "no team", but simulate a failed profile assignment
|
||||
expectAssignProfileResponseFailed = eHost.HardwareSerial
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
addHostsToTeamRequest{TeamID: nil, HostIDs: []uint{eHost.ID}}, http.StatusOK)
|
||||
checkPendingMacOSSetupAssistantJob("hosts_transferred", nil, []string{eHost.HardwareSerial}, 0)
|
||||
|
||||
s.runIntegrationsSchedule()
|
||||
checkAssignProfileRequests(eHost.HardwareSerial, nil)
|
||||
profUUID := profileAssignmentReqs[0].ProfileUUID
|
||||
d := checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, nil, expectNoJobID)
|
||||
require.NotZero(t, d.ResponseUpdatedAt)
|
||||
failedAt := d.ResponseUpdatedAt
|
||||
checkNoJobsPending()
|
||||
// list hosts shows dep profile error
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", true)
|
||||
|
||||
// run the integrations schedule during the cooldown period
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // no new request during cooldown
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// create a new team
|
||||
var tmResp teamResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams", &fleet.Team{
|
||||
Name: t.Name() + "dummy",
|
||||
Description: "desc dummy",
|
||||
}, http.StatusOK, &tmResp)
|
||||
require.NotZero(t, createTeamResp.Team.ID)
|
||||
dummyTeam := tmResp.Team
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
addHostsToTeamRequest{TeamID: &dummyTeam.ID, HostIDs: []uint{eHost.ID}}, http.StatusOK)
|
||||
checkPendingMacOSSetupAssistantJob("hosts_transferred", &dummyTeam.ID, []string{eHost.HardwareSerial}, 0)
|
||||
|
||||
// expect no assign profile request during cooldown
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // screened for cooldown
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// cooldown hosts are screened from update profile jobs that would assign profiles
|
||||
_, err = worker.QueueMacosSetupAssistantJob(ctx, s.ds, kitlog.NewNopLogger(), worker.MacosSetupAssistantUpdateProfile, &dummyTeam.ID, eHost.HardwareSerial)
|
||||
require.NoError(t, err)
|
||||
checkPendingMacOSSetupAssistantJob("update_profile", &dummyTeam.ID, []string{eHost.HardwareSerial}, 0)
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // screened for cooldown
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// cooldown hosts are screened from delete profile jobs that would assign profiles
|
||||
_, err = worker.QueueMacosSetupAssistantJob(ctx, s.ds, kitlog.NewNopLogger(), worker.MacosSetupAssistantProfileDeleted, &dummyTeam.ID, eHost.HardwareSerial)
|
||||
require.NoError(t, err)
|
||||
checkPendingMacOSSetupAssistantJob("profile_deleted", &dummyTeam.ID, []string{eHost.HardwareSerial}, 0)
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // screened for cooldown
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// // TODO: Restore this test when FIXME on DeleteTeam is addressed
|
||||
// s.Do("DELETE", fmt.Sprintf("/api/v1/fleet/teams/%d", dummyTeam.ID), nil, http.StatusOK)
|
||||
// checkPendingMacOSSetupAssistantJob("team_deleted", nil, []string{eHost.HardwareSerial}, 0)
|
||||
// s.runIntegrationsSchedule()
|
||||
// require.Empty(t, profileAssignmentReqs) // screened for cooldown
|
||||
// bySerial = checkHostDEPAssignProfileResponses([]string{eHost.HardwareSerial}, profUUID, fleet.DEPAssignProfileResponseFailed)
|
||||
// d, ok = bySerial[eHost.HardwareSerial]
|
||||
// require.True(t, ok)
|
||||
// require.Equal(t, failedAt, d.ResponseUpdatedAt)
|
||||
// require.Zero(t, d.RetryJobID) // cooling down so no retry job
|
||||
// checkNoJobsPending()
|
||||
|
||||
// transfer back to no team, expect no assign profile request during cooldown
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
addHostsToTeamRequest{TeamID: nil, HostIDs: []uint{eHost.ID}}, http.StatusOK)
|
||||
checkPendingMacOSSetupAssistantJob("hosts_transferred", nil, []string{eHost.HardwareSerial}, 0)
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // screened for cooldown
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// simulate expired cooldown
|
||||
failedAt = failedAt.Add(-2 * time.Hour)
|
||||
setAssignProfileResponseUpdatedAt(eHost.HardwareSerial, failedAt)
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // assign profile request will be made when the retry job is processed on the next worker run
|
||||
d = checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, nil)
|
||||
require.NotZero(t, d.RetryJobID) // retry job created
|
||||
jobID := d.RetryJobID
|
||||
checkPendingMacOSSetupAssistantJob("hosts_cooldown", nil, []string{eHost.HardwareSerial}, jobID)
|
||||
|
||||
// running the DEP schedule should not trigger a profile assignment request when the retry job is pending
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runDEPSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // assign profile request will be made when the retry job is processed on the next worker run
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, &jobID) // no change
|
||||
checkPendingMacOSSetupAssistantJob("hosts_cooldown", nil, []string{eHost.HardwareSerial}, jobID)
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", true)
|
||||
|
||||
// run the inregration schedule and expect success
|
||||
expectAssignProfileResponseFailed = ""
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
checkAssignProfileRequests(eHost.HardwareSerial, &profUUID)
|
||||
d = checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseSuccess, nil, expectNoJobID) // retry job cleared
|
||||
require.True(t, d.ResponseUpdatedAt.After(failedAt))
|
||||
succeededAt := d.ResponseUpdatedAt
|
||||
checkNoJobsPending()
|
||||
checkListHostDEPError(eHost.HardwareSerial, "On (automatic)", false)
|
||||
|
||||
// run the integrations schedule and expect no changes
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs)
|
||||
checkHostCooldown(eHost.HardwareSerial, profUUID, fleet.DEPAssignProfileResponseSuccess, &succeededAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// ingest new device via DEP but the profile assignment fails
|
||||
serial := uuid.NewString()
|
||||
devices = []godep.Device{
|
||||
{SerialNumber: serial, Model: "MacBook Pro", OS: "osx", OpType: "added"},
|
||||
}
|
||||
expectAssignProfileResponseFailed = serial
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runDEPSchedule()
|
||||
checkAssignProfileRequests(serial, nil)
|
||||
profUUID = profileAssignmentReqs[0].ProfileUUID
|
||||
d = checkHostCooldown(serial, profUUID, fleet.DEPAssignProfileResponseFailed, nil, expectNoJobID)
|
||||
require.NotZero(t, d.ResponseUpdatedAt)
|
||||
failedAt = d.ResponseUpdatedAt
|
||||
checkNoJobsPending()
|
||||
h := checkListHostDEPError(serial, "Pending", true) // list hosts shows device pending and dep profile error
|
||||
|
||||
// transfer to team, no profile assignment request is made during the cooldown period
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
addHostsToTeamRequest{TeamID: &team.ID, HostIDs: []uint{h.ID}}, http.StatusOK)
|
||||
checkPendingMacOSSetupAssistantJob("hosts_transferred", &team.ID, []string{serial}, 0)
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // screened by cooldown
|
||||
checkHostCooldown(serial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// run the integrations schedule and expect no changes
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs)
|
||||
checkHostCooldown(serial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
|
||||
// simulate expired cooldown
|
||||
failedAt = failedAt.Add(-2 * time.Hour)
|
||||
setAssignProfileResponseUpdatedAt(serial, failedAt)
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs) // assign profile request will be made when the retry job is processed on the next worker run
|
||||
d = checkHostCooldown(serial, profUUID, fleet.DEPAssignProfileResponseFailed, &failedAt, nil)
|
||||
require.NotZero(t, d.RetryJobID) // retry job created
|
||||
jobID = d.RetryJobID
|
||||
checkPendingMacOSSetupAssistantJob("hosts_cooldown", &team.ID, []string{serial}, jobID)
|
||||
|
||||
// run the inregration schedule and expect success
|
||||
expectAssignProfileResponseFailed = ""
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
checkAssignProfileRequests(serial, nil)
|
||||
require.NotEqual(t, profUUID, profileAssignmentReqs[0].ProfileUUID) // retry job will use the current team profile instead
|
||||
profUUID = profileAssignmentReqs[0].ProfileUUID
|
||||
d = checkHostCooldown(serial, profUUID, fleet.DEPAssignProfileResponseSuccess, nil, expectNoJobID) // retry job cleared
|
||||
require.True(t, d.ResponseUpdatedAt.After(failedAt))
|
||||
checkNoJobsPending()
|
||||
// list hosts shows pending (because MDM detail query hasn't been reported) but dep profile
|
||||
// error has been cleared
|
||||
checkListHostDEPError(serial, "Pending", false)
|
||||
|
||||
// ingest another device via DEP but the profile assignment is not accessible
|
||||
serial = uuid.NewString()
|
||||
devices = []godep.Device{
|
||||
{SerialNumber: serial, Model: "MacBook Pro", OS: "osx", OpType: "added"},
|
||||
}
|
||||
expectAssignProfileResponseNotAccessible = serial
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runDEPSchedule()
|
||||
require.Len(t, profileAssignmentReqs, 2) // FIXME: When new device is added in ABM, we see two profile assign requests when device is not accessible: first during the "fetch" phase, then during the "sync" phase
|
||||
expectProfileUUID := ""
|
||||
for _, req := range profileAssignmentReqs {
|
||||
require.Len(t, req.Devices, 1)
|
||||
require.Equal(t, serial, req.Devices[0])
|
||||
if expectProfileUUID == "" {
|
||||
expectProfileUUID = req.ProfileUUID
|
||||
} else {
|
||||
require.Equal(t, expectProfileUUID, req.ProfileUUID)
|
||||
}
|
||||
d := checkHostCooldown(serial, req.ProfileUUID, fleet.DEPAssignProfileResponseNotAccessible, nil, expectNoJobID) // not accessible responses aren't retried
|
||||
require.NotZero(t, d.ResponseUpdatedAt)
|
||||
failedAt = d.ResponseUpdatedAt
|
||||
}
|
||||
// list hosts shows device pending and no dep profile error for not accessible responses
|
||||
checkListHostDEPError(serial, "Pending", false)
|
||||
|
||||
// no retry job for not accessible responses even if cooldown expires
|
||||
failedAt = failedAt.Add(-2 * time.Hour)
|
||||
setAssignProfileResponseUpdatedAt(serial, failedAt)
|
||||
profileAssignmentReqs = []profileAssignmentReq{}
|
||||
s.runIntegrationsSchedule()
|
||||
require.Empty(t, profileAssignmentReqs)
|
||||
checkHostCooldown(serial, expectProfileUUID, fleet.DEPAssignProfileResponseNotAccessible, &failedAt, expectNoJobID) // no change
|
||||
checkNoJobsPending()
|
||||
}
|
||||
|
||||
func loadEnrollmentProfileDEPToken(t *testing.T, ds *mysql.Datastore) string {
|
||||
@@ -9974,6 +10345,19 @@ func (s *integrationMDMTestSuite) runDEPSchedule() {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) runIntegrationsSchedule() {
|
||||
// FIXME: This pattern (which is being used in testing other schedules as well) seems cause issues
|
||||
// where a subsequent call attempts to trigger when the schedule's trigger channel is full and
|
||||
// schedule ignored the subsquent call (which is the documented behavior of the trigger).
|
||||
// In testing, this can cause the test to hang until the next scheduled run. It isn't a very
|
||||
// noticeable issue here since the intervals for these schedules are short.
|
||||
ch := make(chan bool)
|
||||
s.onIntegrationsScheduleDone = func() { close(ch) }
|
||||
_, err := s.integrationsSchedule.Trigger()
|
||||
require.NoError(s.T(), err)
|
||||
<-ch
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) getRawTokenValue(content string) string {
|
||||
// Create a regex object with the defined pattern
|
||||
pattern := `inputToken.value\s*=\s*'([^']*)'`
|
||||
|
||||
@@ -3,6 +3,7 @@ package worker
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -27,6 +28,7 @@ const (
|
||||
MacosSetupAssistantHostsTransferred MacosSetupAssistantTask = "hosts_transferred"
|
||||
MacosSetupAssistantUpdateAllProfiles MacosSetupAssistantTask = "update_all_profiles"
|
||||
MacosSetupAssistantUpdateProfile MacosSetupAssistantTask = "update_profile"
|
||||
MacosSetupAssistantHostsCooldown MacosSetupAssistantTask = "hosts_cooldown"
|
||||
)
|
||||
|
||||
// MacosSetupAssistant is the job processor for the macos_setup_assistant job.
|
||||
@@ -71,7 +73,9 @@ func (m *MacosSetupAssistant) Run(ctx context.Context, argsJSON json.RawMessage)
|
||||
case MacosSetupAssistantTeamDeleted:
|
||||
return m.runTeamDeleted(ctx, args)
|
||||
case MacosSetupAssistantHostsTransferred:
|
||||
return m.runHostsTransferred(ctx, args)
|
||||
return m.runHostsTransferred(ctx, args, false)
|
||||
case MacosSetupAssistantHostsCooldown:
|
||||
return m.runHostsTransferred(ctx, args, true)
|
||||
case MacosSetupAssistantUpdateAllProfiles:
|
||||
return m.runUpdateAllProfiles(ctx, args)
|
||||
case MacosSetupAssistantUpdateProfile:
|
||||
@@ -113,9 +117,27 @@ func (m *MacosSetupAssistant) runProfileChanged(ctx context.Context, args macosS
|
||||
return ctxerr.Wrap(ctx, err, "list mdm dep serials in team")
|
||||
}
|
||||
if len(serials) > 0 {
|
||||
if _, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, serials...); err != nil {
|
||||
skipSerials, assignSerials, err := m.Datastore.ScreenDEPAssignProfileSerialsForCooldown(ctx, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "run profile changed")
|
||||
}
|
||||
if len(skipSerials) > 0 {
|
||||
// NOTE: the `dep_cooldown` job of the `integrations`` cron picks up the assignments
|
||||
// after the cooldown period is over
|
||||
level.Info(m.Log).Log("msg", "run profile changed: skipping assign profile for devices on cooldown", "serials", fmt.Sprintf("%s", skipSerials))
|
||||
}
|
||||
if len(assignSerials) == 0 {
|
||||
level.Info(m.Log).Log("msg", "run profile changed: no devices to assign profile")
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, assignSerials...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "assign profile")
|
||||
}
|
||||
if err := m.Datastore.UpdateHostDEPAssignProfileResponses(ctx, resp); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "worker: run profile changed")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -163,9 +185,27 @@ func (m *MacosSetupAssistant) runProfileDeleted(ctx context.Context, args macosS
|
||||
return ctxerr.Wrap(ctx, err, "list mdm dep serials in team")
|
||||
}
|
||||
if len(serials) > 0 {
|
||||
if _, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, serials...); err != nil {
|
||||
skipSerials, assignSerials, err := m.Datastore.ScreenDEPAssignProfileSerialsForCooldown(ctx, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "run profile deleted")
|
||||
}
|
||||
if len(skipSerials) > 0 {
|
||||
// NOTE: the `dep_cooldown` job of the `integrations`` cron picks up the assignments
|
||||
// after the cooldown period is over
|
||||
level.Info(m.Log).Log("msg", "run profile deleted: skipping assign profile for devices on cooldown", "serials", fmt.Sprintf("%s", skipSerials))
|
||||
}
|
||||
if len(assignSerials) == 0 {
|
||||
level.Info(m.Log).Log("msg", "run profile deleted: no devices to assign profile")
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, assignSerials...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "assign profile")
|
||||
}
|
||||
if err := m.Datastore.UpdateHostDEPAssignProfileResponses(ctx, resp); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "worker: run profile deleted")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -173,10 +213,10 @@ func (m *MacosSetupAssistant) runProfileDeleted(ctx context.Context, args macosS
|
||||
func (m *MacosSetupAssistant) runTeamDeleted(ctx context.Context, args macosSetupAssistantArgs) error {
|
||||
// team deletion is semantically equivalent to moving hosts to "no team"
|
||||
args.TeamID = nil // should already be this way, but just to make sure
|
||||
return m.runHostsTransferred(ctx, args)
|
||||
return m.runHostsTransferred(ctx, args, false)
|
||||
}
|
||||
|
||||
func (m *MacosSetupAssistant) runHostsTransferred(ctx context.Context, args macosSetupAssistantArgs) error {
|
||||
func (m *MacosSetupAssistant) runHostsTransferred(ctx context.Context, args macosSetupAssistantArgs, fromCooldown bool) error {
|
||||
team, err := m.getTeamNoTeam(ctx, args.TeamID)
|
||||
if err != nil {
|
||||
if fleet.IsNotFound(err) {
|
||||
@@ -205,10 +245,33 @@ func (m *MacosSetupAssistant) runHostsTransferred(ctx context.Context, args maco
|
||||
}
|
||||
}
|
||||
|
||||
_, err = m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, args.HostSerialNumbers...)
|
||||
serials := args.HostSerialNumbers
|
||||
if !fromCooldown {
|
||||
// if not a retry, then we need to screen the serials for cooldown
|
||||
skipSerials, assignSerials, err := m.Datastore.ScreenDEPAssignProfileSerialsForCooldown(ctx, serials)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "run hosts transferred")
|
||||
}
|
||||
if len(skipSerials) > 0 {
|
||||
// NOTE: the `dep_cooldown` job of the `integrations` cron picks up the assignments
|
||||
// after the cooldown period is over
|
||||
level.Info(m.Log).Log("msg", "run hosts transferred: skipping assign profile for devices on cooldown", "serials", fmt.Sprintf("%s", skipSerials))
|
||||
}
|
||||
serials = assignSerials
|
||||
}
|
||||
|
||||
if len(serials) == 0 {
|
||||
level.Info(m.Log).Log("msg", "run hosts transferred: no devices to assign profile")
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := m.DEPClient.AssignProfile(ctx, apple_mdm.DEPName, profUUID, serials...)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "assign profile")
|
||||
}
|
||||
if err := m.Datastore.UpdateHostDEPAssignProfileResponses(ctx, resp); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "worker: run hosts transferred")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -225,7 +288,7 @@ func (m *MacosSetupAssistant) runUpdateAllProfiles(ctx context.Context, args mac
|
||||
teamID = &team.ID
|
||||
}
|
||||
|
||||
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantUpdateProfile, teamID); err != nil {
|
||||
if _, err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantUpdateProfile, teamID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant update profile job")
|
||||
}
|
||||
return nil
|
||||
@@ -254,7 +317,7 @@ func (m *MacosSetupAssistant) runUpdateProfile(ctx context.Context, args macosSe
|
||||
if fleet.IsNotFound(err) {
|
||||
// no setup assistant for that team, enqueue a profile deleted task so
|
||||
// the default profile is assigned to the hosts.
|
||||
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileDeleted, args.TeamID); err != nil {
|
||||
if _, err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileDeleted, args.TeamID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant profile deleted job")
|
||||
}
|
||||
return nil
|
||||
@@ -264,7 +327,7 @@ func (m *MacosSetupAssistant) runUpdateProfile(ctx context.Context, args macosSe
|
||||
|
||||
// no error means that the setup assistant existed for that team, enqueue a profile
|
||||
// changed task so the custom profile is assigned to the hosts.
|
||||
if err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileChanged, args.TeamID); err != nil {
|
||||
if _, err := QueueMacosSetupAssistantJob(ctx, m.Datastore, m.Log, MacosSetupAssistantProfileChanged, args.TeamID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant profile changed job")
|
||||
}
|
||||
return nil
|
||||
@@ -291,7 +354,7 @@ func QueueMacosSetupAssistantJob(
|
||||
task MacosSetupAssistantTask,
|
||||
teamID *uint,
|
||||
serialNumbers ...string,
|
||||
) error {
|
||||
) (uint, error) {
|
||||
attrs := []interface{}{
|
||||
"enabled", "true",
|
||||
macosSetupAssistantJobName, task,
|
||||
@@ -309,8 +372,47 @@ func QueueMacosSetupAssistantJob(
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, macosSetupAssistantJobName, args)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queueing job")
|
||||
return 0, ctxerr.Wrap(ctx, err, "queueing job")
|
||||
}
|
||||
level.Debug(logger).Log("job_id", job.ID)
|
||||
return job.ID, nil
|
||||
}
|
||||
|
||||
func ProcessDEPCooldowns(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger) error {
|
||||
serialsByTeamId, err := ds.GetDEPAssignProfileExpiredCooldowns(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting cooldowns")
|
||||
}
|
||||
if len(serialsByTeamId) == 0 {
|
||||
logger.Log("msg", "no cooldowns to process")
|
||||
return nil
|
||||
}
|
||||
|
||||
// queue job for each team so that macOS setup assistant worker can pick it up and process it
|
||||
for teamID, serials := range serialsByTeamId {
|
||||
if len(serials) == 0 {
|
||||
logger.Log("msg", "no cooldowns", "team_id", teamID)
|
||||
continue
|
||||
}
|
||||
logger.Log("msg", "processing cooldowns", "team_id", teamID, "serials", serials)
|
||||
|
||||
var tid *uint
|
||||
if teamID != 0 {
|
||||
tid = &teamID
|
||||
}
|
||||
|
||||
id, err := QueueMacosSetupAssistantJob(ctx, ds, logger,
|
||||
MacosSetupAssistantHostsCooldown,
|
||||
tid, serials...,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "queue macos setup assistant job for cooldowns")
|
||||
}
|
||||
|
||||
if err := ds.UpdateDEPAssignProfileRetryPending(ctx, id, serials); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "updating dep assign profile retry pending")
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
err = ds.AddHostsToTeam(ctx, &tm2.ID, []uint{hosts[4].ID, hosts[5].ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
var testBMToken = nanodep_client.OAuth1Tokens{
|
||||
testBMToken := nanodep_client.OAuth1Tokens{
|
||||
ConsumerKey: "test_consumer",
|
||||
ConsumerSecret: "test_secret",
|
||||
AccessToken: "test_access_token",
|
||||
@@ -155,7 +155,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
start := time.Now().Truncate(time.Second)
|
||||
|
||||
// enqueue a regenerate all and process the jobs
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, tm1Asst.ID)
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm1.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm1.ID)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -219,7 +219,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
tm2, err = ds.SaveTeam(ctx, tm2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -249,11 +249,11 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
require.NotZero(t, tm3Asst.ID)
|
||||
err = ds.DeleteMDMAppleSetupAssistant(ctx, &tm1.ID)
|
||||
require.NoError(t, err)
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm2.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm2.ID)
|
||||
require.NoError(t, err)
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm3.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, &tm3.ID)
|
||||
require.NoError(t, err)
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileDeleted, &tm1.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileDeleted, &tm1.ID)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -271,7 +271,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
tm2, err = ds.SaveTeam(ctx, tm2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateProfile, &tm2.ID)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -290,9 +290,9 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
err = ds.DeleteTeam(ctx, tm2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantHostsTransferred, &tm3.ID, "serial-2", "serial-4")
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantHostsTransferred, &tm3.ID, "serial-2", "serial-4")
|
||||
require.NoError(t, err)
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantTeamDeleted, nil, "serial-5") // hosts[5] was in team 2
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantTeamDeleted, nil, "serial-5") // hosts[5] was in team 2
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -314,7 +314,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, noTmAsst.ID)
|
||||
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, nil)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantProfileChanged, nil)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
@@ -331,7 +331,7 @@ func TestMacosSetupAssistant(t *testing.T) {
|
||||
reset := time.Now().Truncate(time.Second)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
||||
_, err = QueueMacosSetupAssistantJob(ctx, ds, logger, MacosSetupAssistantUpdateAllProfiles, nil)
|
||||
require.NoError(t, err)
|
||||
runCheckDone()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user