diff --git a/server/datastore/mysql/mdm.go b/server/datastore/mysql/mdm.go index edb5e91e77..a3fc529057 100644 --- a/server/datastore/mysql/mdm.go +++ b/server/datastore/mysql/mdm.go @@ -1281,134 +1281,12 @@ func (ds *Datastore) GetHostMDMProfilesExpectedForVerification(ctx context.Conte switch host.Platform { case "darwin", "ios", "ipados": return ds.getHostMDMAppleProfilesExpectedForVerification(ctx, teamID, host) - case "windows": - return ds.getHostMDMWindowsProfilesExpectedForVerification(ctx, teamID, host.ID) default: return nil, fmt.Errorf("unsupported platform: %s", host.Platform) } } -func (ds *Datastore) getHostMDMWindowsProfilesExpectedForVerification(ctx context.Context, teamID, hostID uint) (map[string]*fleet.ExpectedMDMProfile, error) { - stmt := ` --- profiles without labels -SELECT - mwcp.profile_uuid AS profile_uuid, - name, - syncml AS raw_profile, - min(mwcp.uploaded_at) AS earliest_install_date, - 0 AS count_profile_labels, - 0 AS count_non_broken_labels, - 0 AS count_host_labels -FROM - mdm_windows_configuration_profiles mwcp -WHERE - mwcp.team_id = ? AND - NOT EXISTS ( - SELECT - 1 - FROM - mdm_configuration_profile_labels mcpl - WHERE - mcpl.windows_profile_uuid = mwcp.profile_uuid - ) -GROUP BY profile_uuid, name, syncml - -UNION - --- label-based profiles where the host is a member of all the labels (include-all). --- by design, "include" labels cannot match if they are broken (the host cannot be --- a member of a deleted label). -SELECT - mwcp.profile_uuid AS profile_uuid, - name, - syncml AS raw_profile, - min(mwcp.uploaded_at) AS earliest_install_date, - COUNT(*) AS count_profile_labels, - COUNT(mcpl.label_id) as count_non_broken_labels, - COUNT(lm.label_id) AS count_host_labels -FROM - mdm_windows_configuration_profiles mwcp - JOIN mdm_configuration_profile_labels mcpl - ON mcpl.windows_profile_uuid = mwcp.profile_uuid AND mcpl.exclude = 0 AND mcpl.require_all = 1 - LEFT OUTER JOIN label_membership lm - ON lm.label_id = mcpl.label_id AND lm.host_id = ? -WHERE - mwcp.team_id = ? -GROUP BY - profile_uuid, name, syncml -HAVING - count_profile_labels > 0 AND - count_host_labels = count_profile_labels - -UNION - --- label-based entities where the host is NOT a member of any of the labels (exclude-any). --- explicitly ignore profiles with broken excluded labels so that they are never applied. -SELECT - mwcp.profile_uuid AS profile_uuid, - name, - syncml AS raw_profile, - min(mwcp.uploaded_at) AS earliest_install_date, - COUNT(*) AS count_profile_labels, - COUNT(mcpl.label_id) as count_non_broken_labels, - COUNT(lm.label_id) AS count_host_labels -FROM - mdm_windows_configuration_profiles mwcp - JOIN mdm_configuration_profile_labels mcpl - ON mcpl.windows_profile_uuid = mwcp.profile_uuid AND mcpl.exclude = 1 - LEFT OUTER JOIN label_membership lm - ON lm.label_id = mcpl.label_id AND lm.host_id = ? -WHERE - mwcp.team_id = ? -GROUP BY - profile_uuid, name, syncml -HAVING - -- considers only the profiles with labels, without any broken label, and with the host not in any label - count_profile_labels > 0 AND - count_profile_labels = count_non_broken_labels AND - count_host_labels = 0 - -UNION - --- label-based profiles where the host is a member of at least one of the labels (include-any) -SELECT - mwcp.profile_uuid AS profile_uuid, - name, - syncml AS raw_profile, - min(mwcp.uploaded_at) AS earliest_install_date, - COUNT(*) AS count_profile_labels, - COUNT(mcpl.label_id) as count_non_broken_labels, - COUNT(lm.label_id) AS count_host_labels -FROM - mdm_windows_configuration_profiles mwcp - JOIN mdm_configuration_profile_labels mcpl - ON mcpl.windows_profile_uuid = mwcp.profile_uuid AND mcpl.exclude = 0 AND mcpl.require_all = 0 - LEFT OUTER JOIN label_membership lm - ON lm.label_id = mcpl.label_id AND lm.host_id = ? -WHERE - mwcp.team_id = ? -GROUP BY - profile_uuid, name, syncml -HAVING - count_profile_labels > 0 AND - count_host_labels > 0 -` - var profiles []*fleet.ExpectedMDMProfile - err := sqlx.SelectContext(ctx, ds.reader(ctx), &profiles, stmt, teamID, hostID, teamID, hostID, teamID, hostID, teamID) - if err != nil { - return nil, ctxerr.Wrap(ctx, err, "running query for windows profiles") - } - - byName := make(map[string]*fleet.ExpectedMDMProfile, len(profiles)) - for _, r := range profiles { - byName[r.Name] = r - } - - return byName, nil -} - func (ds *Datastore) getHostMDMAppleProfilesExpectedForVerification(ctx context.Context, teamID uint, host *fleet.Host) (map[string]*fleet.ExpectedMDMProfile, error) { - // TODO This will need to be updated to support scopes stmt := ` -- profiles without labels SELECT @@ -1441,9 +1319,8 @@ WHERE UNION --- label-based profiles where the host is a member of all the labels (include-all) --- by design, "include" labels cannot match if they are broken (the host cannot be --- a member of a deleted label). +-- include-all only (no exclude labels): host must be a member of every include label. +-- broken include labels disqualify the profile. SELECT macp.profile_uuid AS profile_uuid, macp.identifier AS identifier, @@ -1466,7 +1343,11 @@ FROM LEFT OUTER JOIN label_membership lm ON lm.label_id = mcpl.label_id AND lm.host_id = ? WHERE - macp.team_id = ? + macp.team_id = ? AND + NOT EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 1 + ) GROUP BY profile_uuid, identifier HAVING @@ -1475,8 +1356,8 @@ HAVING UNION --- label-based entities where the host is NOT a member of any of the labels (exclude-any). --- explicitly ignore profiles with broken excluded labels so that they are never applied. +-- exclude-any only (no include labels): host must NOT be a member of any exclude label. +-- broken or not-yet-scanned dynamic exclude labels disqualify the profile. SELECT macp.profile_uuid AS profile_uuid, macp.identifier AS identifier, @@ -1499,7 +1380,11 @@ FROM LEFT OUTER JOIN label_membership lm ON lm.label_id = mcpl.label_id AND lm.host_id = ? WHERE - macp.team_id = ? + macp.team_id = ? AND + NOT EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 0 + ) GROUP BY profile_uuid, identifier HAVING @@ -1510,7 +1395,8 @@ HAVING UNION --- label-based profiles where the host is a member of at least one of the labels (include-any) +-- include-any only (no exclude labels): host must be a member of at least one include label. +-- broken include labels are skipped (host can't be a member of a deleted label). SELECT macp.profile_uuid AS profile_uuid, macp.identifier AS identifier, @@ -1533,16 +1419,124 @@ FROM LEFT OUTER JOIN label_membership lm ON lm.label_id = mcpl.label_id AND lm.host_id = ? WHERE - macp.team_id = ? + macp.team_id = ? AND + NOT EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 1 + ) GROUP BY profile_uuid, identifier HAVING count_profile_labels > 0 AND count_host_labels > 0 + +UNION + +-- include-all + exclude-any: host must be in ALL include labels AND NOT in ANY exclude label. +-- broken include labels or broken/not-yet-scanned dynamic exclude labels disqualify the profile. +SELECT + macp.profile_uuid AS profile_uuid, + macp.identifier AS identifier, + SUM(CASE WHEN mcpl.exclude = 0 THEN 1 ELSE 0 END) as count_profile_labels, + SUM(CASE WHEN mcpl.exclude = 0 AND mcpl.label_id IS NOT NULL THEN 1 ELSE 0 END) as count_non_broken_labels, + SUM(CASE WHEN mcpl.exclude = 0 AND lm_inc.label_id IS NOT NULL THEN 1 ELSE 0 END) as count_host_labels, + min(earliest_install_date) AS earliest_install_date +FROM + mdm_apple_configuration_profiles macp + JOIN ( + SELECT + checksum, + min(uploaded_at) AS earliest_install_date + FROM + mdm_apple_configuration_profiles + GROUP BY checksum + ) cs ON macp.checksum = cs.checksum + JOIN mdm_configuration_profile_labels mcpl + ON mcpl.apple_profile_uuid = macp.profile_uuid + JOIN hosts h + ON h.id = ? + LEFT OUTER JOIN labels lbl + ON lbl.id = mcpl.label_id + LEFT OUTER JOIN label_membership lm_inc + ON lm_inc.label_id = mcpl.label_id AND lm_inc.host_id = ? AND mcpl.exclude = 0 + LEFT OUTER JOIN label_membership lm_exc + ON lm_exc.label_id = mcpl.label_id AND lm_exc.host_id = ? AND mcpl.exclude = 1 +WHERE + macp.team_id = ? AND + EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 0 AND require_all = 1 + ) AND + EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 1 + ) +GROUP BY + profile_uuid, identifier +HAVING + -- include gate: host in all include labels (no broken include labels) + count_profile_labels > 0 AND count_non_broken_labels = count_profile_labels AND count_host_labels = count_profile_labels AND + -- exclude gate: host not in any exclude label, no broken/unscanned exclude labels (reusing count_host_updated_after_labels) + SUM(CASE WHEN mcpl.exclude = 1 AND lm_exc.label_id IS NOT NULL THEN 1 + WHEN mcpl.exclude = 1 AND (lbl.label_membership_type = 0 AND lbl.created_at IS NOT NULL AND h.label_updated_at < lbl.created_at) THEN 1 + WHEN mcpl.exclude = 1 AND mcpl.label_id IS NULL THEN 1 + ELSE 0 END) = 0 + +UNION + +-- include-any + exclude-any: host must be in AT LEAST ONE include label AND NOT in ANY exclude label. +-- broken/not-yet-scanned dynamic exclude labels disqualify the profile. +SELECT + macp.profile_uuid AS profile_uuid, + macp.identifier AS identifier, + SUM(CASE WHEN mcpl.exclude = 0 THEN 1 ELSE 0 END) as count_profile_labels, + SUM(CASE WHEN mcpl.exclude = 0 AND mcpl.label_id IS NOT NULL THEN 1 ELSE 0 END) as count_non_broken_labels, + SUM(CASE WHEN mcpl.exclude = 0 AND lm_inc.label_id IS NOT NULL THEN 1 ELSE 0 END) as count_host_labels, + min(earliest_install_date) AS earliest_install_date +FROM + mdm_apple_configuration_profiles macp + JOIN ( + SELECT + checksum, + min(uploaded_at) AS earliest_install_date + FROM + mdm_apple_configuration_profiles + GROUP BY checksum + ) cs ON macp.checksum = cs.checksum + JOIN mdm_configuration_profile_labels mcpl + ON mcpl.apple_profile_uuid = macp.profile_uuid + JOIN hosts h + ON h.id = ? + LEFT OUTER JOIN labels lbl + ON lbl.id = mcpl.label_id + LEFT OUTER JOIN label_membership lm_inc + ON lm_inc.label_id = mcpl.label_id AND lm_inc.host_id = ? AND mcpl.exclude = 0 + LEFT OUTER JOIN label_membership lm_exc + ON lm_exc.label_id = mcpl.label_id AND lm_exc.host_id = ? AND mcpl.exclude = 1 +WHERE + macp.team_id = ? AND + EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 0 AND require_all = 0 + ) AND + EXISTS ( + SELECT 1 FROM mdm_configuration_profile_labels + WHERE apple_profile_uuid = macp.profile_uuid AND exclude = 1 + ) +GROUP BY + profile_uuid, identifier +HAVING + -- include gate: host in at least one include label + count_host_labels >= 1 AND + -- exclude gate: host not in any exclude label, no broken/unscanned exclude labels + SUM(CASE WHEN mcpl.exclude = 1 AND lm_exc.label_id IS NOT NULL THEN 1 + WHEN mcpl.exclude = 1 AND (lbl.label_membership_type = 0 AND lbl.created_at IS NOT NULL AND h.label_updated_at < lbl.created_at) THEN 1 + WHEN mcpl.exclude = 1 AND mcpl.label_id IS NULL THEN 1 + ELSE 0 END) = 0 ` var rows []*fleet.ExpectedMDMProfile - if err := sqlx.SelectContext(ctx, ds.reader(ctx), &rows, stmt, teamID, host.ID, teamID, host.ID, teamID, host.ID, teamID); err != nil { + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &rows, stmt, teamID, host.ID, teamID, host.ID, teamID, host.ID, teamID, host.ID, host.ID, host.ID, teamID, host.ID, host.ID, host.ID, teamID); err != nil { return nil, ctxerr.Wrap(ctx, err, fmt.Sprintf("getting expected profiles for host in team %d", teamID)) } diff --git a/server/datastore/mysql/mdm_test.go b/server/datastore/mysql/mdm_test.go index 9602af8319..ffceab4730 100644 --- a/server/datastore/mysql/mdm_test.go +++ b/server/datastore/mysql/mdm_test.go @@ -2560,6 +2560,16 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore) configProfileForTest(t, "exclude_one_matches_prof", "exclude_one_matches_prof", "p", excludeMatchedLabel1, excludeUnmatchedLabel), // This profile will use an "exclude" rule where the host has none of the labels, thus should be included configProfileForTest(t, "exclude_none_match_prof", "exclude_none_match_prof", "q", excludeUnmatchedLabel), + + // This profile will use both an "include all" and "exclude" rule where the host matches the include all rule but also matches the exclude rule, thus should be excluded + configProfileForTest(t, "include_all_and_exclude_match_prof", "include_all_and_exclude_match_prof", "r", includeAllMatchedLabel1, includeAllMatchedLabel2, excludeMatchedLabel1), + // This profile will use both an "include all" and "exclude" rule where the host matches the include all rule but does not match the exclude rule, thus should be included + configProfileForTest(t, "include_all_and_exclude_none_match_prof", "include_all_and_exclude_none_match_prof", "s", includeAllMatchedLabel1, includeAllMatchedLabel2, excludeUnmatchedLabel), + + // This profile will use both an "include any" and "exclude" rule where the host matches the include any rule but also matches the exclude rule, thus should be excluded + configProfileForTest(t, "include_any_and_exclude_match_prof", "include_any_and_exclude_match_prof", "t", includeAnyMatchedLabel1, includeAnyUnmatchedLabel, excludeMatchedLabel1), + // This profile will use both an "include any" and "exclude" rule where the host matches the include any rule but does not match the exclude rule, thus should be included + configProfileForTest(t, "include_any_and_exclude_none_match_prof", "include_any_and_exclude_none_match_prof", "u", includeAnyMatchedLabel1, includeAnyUnmatchedLabel, excludeUnmatchedLabel), } updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, profiles, nil, nil, nil, nil) @@ -2593,432 +2603,16 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore) profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) require.NoError(t, err) - require.Len(t, profs, 11) - - return team.ID, host - } - - // =================================================== - // Windows - // =================================================== - - windowsBasicTeamProfNoLabelsSetup := func() (uint, *fleet.Host) { - host, err := ds.NewHost(ctx, &fleet.Host{ - Hostname: "windows-test", - OsqueryHostID: ptr.String("osquery-windows"), - NodeKey: ptr.String("node-key-windows"), - UUID: uuid.NewString(), - Platform: "windows", - }) - require.NoError(t, err) - windowsEnroll(t, ds, host) - - // create a team - team, err := ds.NewTeam(ctx, &fleet.Team{Name: "windows team 1"}) - require.NoError(t, err) - - err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host.ID})) - require.NoError(t, err) - - // create profiles for team 1 - profiles := []*fleet.MDMWindowsConfigProfile{ - windowsConfigProfileForTest(t, "T1.1", "T1.1"), - windowsConfigProfileForTest(t, "T1.2", "T1.2"), - } - - updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil, nil, nil) - require.NoError(t, err) - assert.False(t, updates.AppleConfigProfile) - assert.True(t, updates.WindowsConfigProfile) - assert.False(t, updates.AppleDeclaration) - - profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 2) - - return team.ID, host - } - - windowsLabeledTeamProfSetup := func() (uint, *fleet.Host) { - host, err := ds.NewHost(ctx, &fleet.Host{ - Hostname: "windows-test-2", - OsqueryHostID: ptr.String("osquery-windows-2"), - NodeKey: ptr.String("node-key-windows-2"), - UUID: uuid.NewString(), - Platform: "windows", - }) - require.NoError(t, err) - windowsEnroll(t, ds, host) - - // create a team - team, err := ds.NewTeam(ctx, &fleet.Team{Name: "windows team 2"}) - require.NoError(t, err) - - err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host.ID})) - require.NoError(t, err) - - // create profiles for team 1 - profiles := []*fleet.MDMWindowsConfigProfile{ - windowsConfigProfileForTest(t, "T2.1", "T2.1"), - windowsConfigProfileForTest(t, "T2.2", "T2.2"), - windowsConfigProfileForTest(t, "labeled_prof", "labeled_prof"), - } - - label, err := ds.NewLabel(ctx, &fleet.Label{Name: "test_label_6"}) - require.NoError(t, err) - - updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil, nil, nil) - require.NoError(t, err) - assert.False(t, updates.AppleConfigProfile) - assert.True(t, updates.WindowsConfigProfile) - assert.False(t, updates.AppleDeclaration) - - var uid string - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - return sqlx.GetContext( - ctx, - q, - &uid, - `SELECT profile_uuid FROM mdm_windows_configuration_profiles WHERE name = ?`, - "labeled_prof", - ) - }) - - // Update label with host membership - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, ?)", - host.ID, - label.ID, - ) - return err - }, - ) - - // Update profile <-> label mapping - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT INTO mdm_configuration_profile_labels (windows_profile_uuid, label_name, label_id) VALUES (?, ?, ?)", - uid, - label.Name, - label.ID, - ) - return err - }, - ) - - profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 3) - - return team.ID, host - } - - windowsLabeledTeamProfWithAdditionalLabeledProfSetup := func() (uint, *fleet.Host) { - host, err := ds.NewHost(ctx, &fleet.Host{ - Hostname: "windows-test-3", - OsqueryHostID: ptr.String("osquery-windows-3"), - NodeKey: ptr.String("node-key-windows-3"), - UUID: uuid.NewString(), - Platform: "windows", - }) - require.NoError(t, err) - windowsEnroll(t, ds, host) - - // create a team - team, err := ds.NewTeam(ctx, &fleet.Team{Name: "windows team 3"}) - require.NoError(t, err) - - err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host.ID})) - require.NoError(t, err) - - // create profiles for team 1 - profiles := []*fleet.MDMWindowsConfigProfile{ - windowsConfigProfileForTest(t, "T3.1", "T3.1"), - windowsConfigProfileForTest(t, "T3.2", "T3.7"), - windowsConfigProfileForTest(t, "labeled_prof_2", "labeled_prof_2"), - } - - testLabel2, err := ds.NewLabel(ctx, &fleet.Label{Name: uuid.NewString()}) - require.NoError(t, err) - - testLabel3, err := ds.NewLabel(ctx, &fleet.Label{Name: uuid.NewString()}) - require.NoError(t, err) - - updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil, nil, nil) - require.NoError(t, err) - assert.False(t, updates.AppleConfigProfile) - assert.True(t, updates.WindowsConfigProfile) - assert.False(t, updates.AppleDeclaration) - - var uid string - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - return sqlx.GetContext( - ctx, - q, - &uid, - `SELECT profile_uuid FROM mdm_windows_configuration_profiles WHERE name = ?`, - "labeled_prof_2", - ) - }) - - // Update label with host membership - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, ?)", - host.ID, - testLabel2.ID, - ) - return err - }, - ) - - // Update profile <-> label mapping - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT INTO mdm_configuration_profile_labels (windows_profile_uuid, label_name, label_id) VALUES (?, ?, ?)", - uid, - testLabel2.Name, - testLabel2.ID, - ) - return err - }, - ) - - // Also add mapping to test label 3 - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT INTO mdm_configuration_profile_labels (windows_profile_uuid, label_name, label_id) VALUES (?, ?, ?)", - uid, - testLabel3.Name, - testLabel3.ID, - ) - return err - }, - ) - - profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 3) - - return team.ID, host - } - - windowsProfWithBrokenLabelSetup := func() (uint, *fleet.Host) { - host, err := ds.NewHost(ctx, &fleet.Host{ - Hostname: "windows-test-4", - OsqueryHostID: ptr.String("osquery-windows-4"), - NodeKey: ptr.String("node-key-windows-4"), - UUID: uuid.NewString(), - Platform: "windows", - }) - require.NoError(t, err) - windowsEnroll(t, ds, host) - - // create a team - team, err := ds.NewTeam(ctx, &fleet.Team{Name: "windows team 4"}) - require.NoError(t, err) - - err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host.ID})) - require.NoError(t, err) - - // create profiles for team - profiles := []*fleet.MDMWindowsConfigProfile{ - windowsConfigProfileForTest(t, "T4.1", "T4.1"), - windowsConfigProfileForTest(t, "T4.2", "T4.2"), - windowsConfigProfileForTest(t, "broken_label_prof", "broken_label_prof"), - } - - label, err := ds.NewLabel(ctx, &fleet.Label{Name: uuid.NewString()}) - require.NoError(t, err) - - updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil, nil, nil) - require.NoError(t, err) - assert.False(t, updates.AppleConfigProfile) - assert.True(t, updates.WindowsConfigProfile) - assert.False(t, updates.AppleDeclaration) - - var uid string - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - return sqlx.GetContext( - ctx, - q, - &uid, - `SELECT profile_uuid FROM mdm_windows_configuration_profiles WHERE name = ?`, - "broken_label_prof", - ) - }) - - // Update label with host membership - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, ?)", - host.ID, - label.ID, - ) - return err - }, - ) - - // Update profile <-> label mapping - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT INTO mdm_configuration_profile_labels (windows_profile_uuid, label_name, label_id) VALUES (?, ?, ?)", - uid, - label.Name, - label.ID, - ) - return err - }, - ) - - profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 3) - - // Simulate the label being broken — direct DeleteLabel is now blocked when - // referenced by a profile, so we nullify label_id in the join tables instead. - ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { - if _, err := q.ExecContext(ctx, `UPDATE mdm_configuration_profile_labels SET label_id = NULL WHERE label_id = ?`, label.ID); err != nil { - return err - } - _, err := q.ExecContext(ctx, `UPDATE mdm_declaration_labels SET label_id = NULL WHERE label_id = ?`, label.ID) - return err - }) - - return team.ID, host - } - - windowsLabeledProfileRulesSetup := func() (uint, *fleet.Host) { - host, err := ds.NewHost(ctx, &fleet.Host{ - Hostname: "windows-test-5", - OsqueryHostID: ptr.String("osquery-windows-5"), - NodeKey: ptr.String("node-key-windows-5"), - UUID: uuid.NewString(), - Platform: "windows", - }) - require.NoError(t, err) - windowsEnroll(t, ds, host) - - // create a team - team, err := ds.NewTeam(ctx, &fleet.Team{Name: "windows team 5"}) - require.NoError(t, err) - - err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&team.ID, []uint{host.ID})) - require.NoError(t, err) - - // Include any labels - includeAnyMatchedLabel1, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-any-windows-matched-label-1"}) - require.NoError(t, err) - includeAnyMatchedLabel2, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-any-windows-matched-label-2"}) - require.NoError(t, err) - includeAnyUnmatchedLabel, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-any-windows-unmatched-label"}) - require.NoError(t, err) - - // Include all labels - includeAllMatchedLabel1, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-all-windows-matched-label-1"}) - require.NoError(t, err) - includeAllMatchedLabel2, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-all-windows-matched-label-2"}) - require.NoError(t, err) - includeAllUnmatchedLabel, err := ds.NewLabel(ctx, &fleet.Label{Name: "include-all-windows-unmatched-label"}) - require.NoError(t, err) - - // Exclude labels - excludeMatchedLabel1, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-windows-matched-label-1"}) - require.NoError(t, err) - excludeMatchedLabel2, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-windows-matched-label-2"}) - require.NoError(t, err) - excludeUnmatchedLabel, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-windows-unmatched-label"}) - require.NoError(t, err) - - // create profiles for team - // include_any_all_match_prof - // include_any_one_matches_prof - // include_all_all_match_prof - // exclude_none_match_prof - profiles := []*fleet.MDMWindowsConfigProfile{ - // Two profiles with no label rules, thus should always be included - windowsConfigProfileForTest(t, "T5.1", "T5.1"), - windowsConfigProfileForTest(t, "T5.2", "T5.2"), - - // This profile will use an "include any" rule where the host has both of the labels, thus should be included - windowsConfigProfileForTest(t, "include_any_all_match_prof", "include_any_all_match_prof", includeAnyMatchedLabel1, includeAnyMatchedLabel2), - // This profile will use an "include any" rule where the host has one of the labels, thus should be included - windowsConfigProfileForTest(t, "include_any_one_matches_prof", "include_any_one_matches_prof", includeAnyMatchedLabel1, includeAnyUnmatchedLabel), - // This profile will use an "include any" rule where the host has none of the labels, thus should be excluded - windowsConfigProfileForTest(t, "include_any_none_match_prof", "include_any_none_match_prof", includeAnyUnmatchedLabel), - - // This profile will use an "include all" rule where the host has all of the labels and thus should be included - windowsConfigProfileForTest(t, "include_all_all_match_prof", "include_all_all_match_prof", includeAllMatchedLabel1, includeAllMatchedLabel2), - // This profile will use an "include all" rule where the host has one of the labels and thus should be excluded - windowsConfigProfileForTest(t, "include_all_one_matches_prof", "include_all_one_matches_prof", includeAllMatchedLabel1, includeAllUnmatchedLabel), - // This profile will use an "include any" rule where the host has none of the labels and thus should be excluded - windowsConfigProfileForTest(t, "include_all_none_match_prof", "include_all_none_match_prof", includeAllUnmatchedLabel), - - // This profile will use an "exclude" rule where the host has both of the labels, thus should be excluded - windowsConfigProfileForTest(t, "exclude_all_match_prof", "exclude_all_match_prof", excludeMatchedLabel1, excludeMatchedLabel2), - // This profile will use an "exclude" rule where the host has one of the labels, thus should be excluded - windowsConfigProfileForTest(t, "exclude_one_matches_prof", "exclude_one_matches_prof", excludeMatchedLabel1, excludeUnmatchedLabel), - // This profile will use an "exclude" rule where the host has none of the labels, thus should be included - windowsConfigProfileForTest(t, "exclude_none_match_prof", "exclude_none_match_prof", excludeUnmatchedLabel), - } - - updates, err := ds.BatchSetMDMProfiles(ctx, &team.ID, nil, profiles, nil, nil, nil) - require.NoError(t, err) - assert.False(t, updates.AppleConfigProfile) - assert.True(t, updates.WindowsConfigProfile) - assert.False(t, updates.AppleDeclaration) - - // Update labels with host membership - ExecAdhocSQL( - t, ds, func(db sqlx.ExtContext) error { - _, err := db.ExecContext( - context.Background(), - "INSERT IGNORE INTO label_membership (host_id, label_id) VALUES (?, ?), (?, ?), (?, ?), (?, ?), (?, ?), (?, ?)", - host.ID, - includeAnyMatchedLabel1.ID, - host.ID, - includeAnyMatchedLabel2.ID, - host.ID, - includeAllMatchedLabel1.ID, - host.ID, - includeAllMatchedLabel2.ID, - host.ID, - excludeMatchedLabel1.ID, - host.ID, - excludeMatchedLabel2.ID, - ) - return err - }, - ) - - profs, _, err := ds.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 11) + require.Len(t, profs, 15) return team.ID, host } tests := []struct { - name string - setupFunc func() (uint, *fleet.Host) - wantMac map[string]*fleet.ExpectedMDMProfile - wantWindows map[string]*fleet.ExpectedMDMProfile - os string + name string + setupFunc func() (uint, *fleet.Host) + wantMac map[string]*fleet.ExpectedMDMProfile + os string }{ { name: "macos basic team profiles no labels", @@ -3070,61 +2664,14 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore) name: "macos labels include any/all and exclude rules", setupFunc: macosLabeledProfileRulesSetup, wantMac: map[string]*fleet.ExpectedMDMProfile{ - "T6.1": {Identifier: "T6.1"}, - "T6.2": {Identifier: "T6.2"}, - "include_any_all_match_prof": {Identifier: "include_any_all_match_prof"}, - "include_any_one_matches_prof": {Identifier: "include_any_one_matches_prof"}, - "include_all_all_match_prof": {Identifier: "include_all_all_match_prof"}, - "exclude_none_match_prof": {Identifier: "exclude_none_match_prof"}, - }, - }, - { - name: "windows basic team profiles no labels", - setupFunc: windowsBasicTeamProfNoLabelsSetup, - wantWindows: map[string]*fleet.ExpectedMDMProfile{ - "T1.1": {Name: "T1.1"}, - "T1.2": {Name: "T1.2"}, - }, - }, - { - name: "windows labeled team profile", - setupFunc: windowsLabeledTeamProfSetup, - wantWindows: map[string]*fleet.ExpectedMDMProfile{ - "T2.1": {Name: "T2.1"}, - "T2.2": {Name: "T2.2"}, - "labeled_prof": {Name: "labeled_prof"}, - }, - }, - { - name: "windows labeled team profile with additional labeled profile", - setupFunc: windowsLabeledTeamProfWithAdditionalLabeledProfSetup, - // Our expected profiles should not include the labeled profile, because it - // maps to a label that is not applied to the host. - wantWindows: map[string]*fleet.ExpectedMDMProfile{ - "T3.1": {Name: "T3.1"}, - "T3.2": {Name: "T3.2"}, - }, - }, - { - name: "windows profile with broken label", - setupFunc: windowsProfWithBrokenLabelSetup, - // Our expected profiles should not include the labeled profile, because it is broken - // (the label was deleted) - wantWindows: map[string]*fleet.ExpectedMDMProfile{ - "T4.1": {Name: "T4.1"}, - "T4.2": {Name: "T4.2"}, - }, - }, - { - name: "windows labels include any/all and exclude rules", - setupFunc: windowsLabeledProfileRulesSetup, - wantWindows: map[string]*fleet.ExpectedMDMProfile{ - "T5.1": {Name: "T5.1"}, - "T5.2": {Name: "T5.2"}, - "include_any_all_match_prof": {Name: "include_any_all_match_prof"}, - "include_any_one_matches_prof": {Name: "include_any_one_matches_prof"}, - "include_all_all_match_prof": {Name: "include_all_all_match_prof"}, - "exclude_none_match_prof": {Name: "exclude_none_match_prof"}, + "T6.1": {Identifier: "T6.1"}, + "T6.2": {Identifier: "T6.2"}, + "include_any_all_match_prof": {Identifier: "include_any_all_match_prof"}, + "include_any_one_matches_prof": {Identifier: "include_any_one_matches_prof"}, + "include_all_all_match_prof": {Identifier: "include_all_all_match_prof"}, + "exclude_none_match_prof": {Identifier: "exclude_none_match_prof"}, + "include_all_and_exclude_none_match_prof": {Identifier: "include_all_and_exclude_none_match_prof"}, + "include_any_and_exclude_none_match_prof": {Identifier: "include_any_and_exclude_none_match_prof"}, }, }, } @@ -3144,16 +2691,6 @@ func testGetHostMDMProfilesExpectedForVerification(t *testing.T, ds *Datastore) } } } - - if len(tt.wantWindows) > 0 { - got, err := ds.getHostMDMWindowsProfilesExpectedForVerification(ctx, teamID, host.ID) - require.NoError(t, err) - for k, v := range tt.wantWindows { - require.Contains(t, got, k) - require.Equal(t, v.Name, got[k].Name) - // windows does not currently use or care about earliest install date - } - } }) } } diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index e44d245078..dd6d8f3415 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -6132,9 +6132,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { if _, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_declarations SET status = ? WHERE status = ?`, fleet.OSSettingsVerifying, fleet.OSSettingsPending); err != nil { return err } - if _, err := q.ExecContext(ctx, `UPDATE host_mdm_windows_profiles SET status = ? WHERE status = ?`, fleet.OSSettingsVerifying, fleet.OSSettingsPending); err != nil { - return err - } return nil }) } @@ -6152,7 +6149,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { // create an Apple and a Windows host appleHost, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t) - windowsHost, _ := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t) // create a few labels labels := make([]*fleet.Label, 5) @@ -6163,16 +6159,12 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { } // simulate reporting label results for those hosts appleHost.LabelUpdatedAt = time.Now() - windowsHost.LabelUpdatedAt = time.Now() err := s.ds.UpdateHost(ctx, appleHost) require.NoError(t, err) - err = s.ds.UpdateHost(ctx, windowsHost) - require.NoError(t, err) // set an Apple profile and declaration and a Windows profile s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{ {Name: "A1", Contents: mobileconfigForTest("A1", "A1"), LabelsExcludeAny: []string{labels[0].Name, labels[1].Name}}, - {Name: "W2", Contents: syncMLForTest("./Foo/W2"), LabelsExcludeAny: []string{labels[2].Name, labels[3].Name}}, {Name: "D3", Contents: declarationForTest("D3"), LabelsExcludeAny: []string{labels[4].Name}}, }}, http.StatusNoContent) @@ -6188,11 +6180,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryPending}, }, }) - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryPending}, - }, - }) err = s.keyValueStore.Delete(ctx, fleet.MDMProfileProcessingKeyPrefix+":"+appleHost.UUID) require.NoError(t, err) @@ -6207,21 +6194,12 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, - }, - }) // mark some profiles as verified (despite accepting a HostMacOSProfile struct, it supports Windows too) err = apple_mdm.VerifyHostMDMProfiles(ctx, s.ds, appleHost, map[string]*fleet.HostMacOSProfile{ "A1": {Identifier: "A1", DisplayName: "A1", InstallDate: time.Now()}, }) require.NoError(t, err) - err = apple_mdm.VerifyHostMDMProfiles(ctx, s.ds, windowsHost, map[string]*fleet.HostMacOSProfile{ - "W2": {Identifier: "W2", DisplayName: "W2", InstallDate: time.Now()}, - }) - require.NoError(t, err) s.assertHostAppleConfigProfiles(map[*fleet.Host][]fleet.HostMDMAppleProfile{ appleHost: { @@ -6231,11 +6209,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerified}, - }, - }) // make hosts members of labels [1], [2], [3] and [4], meaning that none of the profiles apply anymore err = s.ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{ @@ -6243,10 +6216,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {labels[2].ID, appleHost.ID}, {labels[3].ID, appleHost.ID}, {labels[4].ID, appleHost.ID}, - {labels[1].ID, windowsHost.ID}, - {labels[2].ID, windowsHost.ID}, - {labels[3].ID, windowsHost.ID}, - {labels[4].ID, windowsHost.ID}, }) require.NoError(t, err) @@ -6259,19 +6228,12 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - // windows profiles now get marked for removal with a pending delete command - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeRemove, Status: &fleet.MDMDeliveryPending}, - }, - }) - // remove membership of labels [2] for Windows, and [4] for Apple, meaning + // remove membership of [4] for Apple, meaning // that only D3 will be installed on Apple (as the Windows host is still // member of an excluded label) err = s.ds.AsyncBatchDeleteLabelMembership(ctx, [][2]uint{ {labels[4].ID, appleHost.ID}, - {labels[2].ID, windowsHost.ID}, }) require.NoError(t, err) @@ -6284,11 +6246,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeRemove, Status: &fleet.MDMDeliveryPending}, - }, - }) // remove label [3] as an excluded label for the Windows profile, meaning // that the host now meets the requirement to install. @@ -6307,22 +6264,9 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - // The batch set above removed label [3] as an exclude label for W2, so the - // host now meets the requirement and W2 is re-installed (the install query - // detects profiles in desired state with operation_type='remove' and flips - // them back to install). - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryPending}, - }, - }) // simulate the reconcile profiles deployment and mark as verified triggerReconcileProfiles() - err = apple_mdm.VerifyHostMDMProfiles(ctx, s.ds, windowsHost, map[string]*fleet.HostMacOSProfile{ - "W2": {Identifier: "W2", DisplayName: "W2", InstallDate: time.Now()}, - }) - require.NoError(t, err) s.assertHostAppleConfigProfiles(map[*fleet.Host][]fleet.HostMDMAppleProfile{ appleHost: { @@ -6331,13 +6275,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - // W2 was re-installed (flipped from remove to install) and is now - // install+pending after the reconciler sent the command. - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerified}, - }, - }) // simulate the A1 profile being broken by nullifying labels[1] in the join table // (direct DeleteLabel is now blocked when a label is referenced by a profile) @@ -6355,12 +6292,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - // W2 is still install+pending from the re-install triggered earlier. - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerified}, - }, - }) // it also doesn't get installed to a new host not a member of any labels appleHost2, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t) @@ -6380,18 +6311,10 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerified}, - }, - }) // simulate D3 and W2 profiles being broken by nullifying labels[2] and [4] // in the join table (DeleteLabel is now blocked when referenced by a profile). mysqltest.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error { - if _, err := q.ExecContext(ctx, `UPDATE mdm_configuration_profile_labels SET label_id = NULL WHERE label_id = ?`, labels[2].ID); err != nil { - return err - } _, err := q.ExecContext(ctx, `UPDATE mdm_configuration_profile_labels SET label_id = NULL WHERE label_id = ?`, labels[4].ID) return err }) @@ -6409,13 +6332,6 @@ func (s *integrationMDMTestSuite) TestHostMDMProfilesExcludeLabels() { {Identifier: mobileconfig.FleetCARootConfigPayloadIdentifier, OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerifying}, }, }) - // W2 references a deleted label, so the reconciler skips it entirely - // (it appears in neither the install nor the remove list). - s.assertHostWindowsConfigProfiles(map[*fleet.Host][]fleet.HostMDMWindowsProfile{ - windowsHost: { - {Name: "W2", OperationType: fleet.MDMOperationTypeInstall, Status: &fleet.MDMDeliveryVerified}, - }, - }) } func (s *integrationMDMTestSuite) TestMDMProfilesIncludeAnyLabels() {