update apple query for profile verification with include all/any + exclude (#47079)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Follow up PR for the Include Any/All + Exclude label story, since this was missed. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved Apple MDM profile verification to more accurately determine expected configuration profiles, including correct handling of mixed include/exclude label combinations and exclusion edge cases. * **Tests** * Updated unit and integration tests to reflect the expanded Apple include+exclude scenarios and to run Apple-only profile reconciliation flows. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+126
-132
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user