Fix exclude-any label scoping on non-osquery platforms (#35353)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #33132 Changes label scoping logic to handle manual labels without needing the host's label_updated_at at all and to update Android/iOS hosts' label_updated_at on checkins so they update at a similar cadence to platforms where they're actually supported and should we ever support queries on those hosts should "just work" # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] 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) - [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] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually For unreleased bug fixes in a release candidate, one of: - [x] Confirmed that the fix is not expected to adversely impact load test results - [x] Alerted the release DRI if additional load testing is needed --------- Co-authored-by: Ian Littman <iansltx@gmail.com>
This commit is contained in:
co-authored by
Ian Littman
parent
01108a4d5a
commit
5679052134
@@ -0,0 +1 @@
|
||||
* Fixed an issue where "Exclude Any" label scoping did work properly for iOS, iPadOS and Android hosts
|
||||
@@ -857,8 +857,13 @@ const androidApplicableProfilesQuery = `
|
||||
COUNT(mcpl.label_id) as count_non_broken_labels,
|
||||
COUNT(lm.label_id) as count_host_labels,
|
||||
-- this helps avoid the case where the host is not a member of a label
|
||||
-- just because it hasn't reported results for that label yet.
|
||||
SUM(CASE WHEN lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1 ELSE 0 END) as count_host_updated_after_labels
|
||||
-- just because it hasn't reported results for that label yet. But we
|
||||
-- only need consider this for dynamic labels - manual(type=1) can be
|
||||
-- considered at any time
|
||||
SUM(
|
||||
CASE WHEN lbl.label_membership_type <> 1 AND lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1
|
||||
WHEN lbl.label_membership_type = 1 AND lbl.created_at IS NOT NULL THEN 1
|
||||
ELSE 0 END) as count_host_updated_after_labels
|
||||
FROM
|
||||
mdm_android_configuration_profiles macp
|
||||
JOIN hosts h
|
||||
|
||||
@@ -36,6 +36,7 @@ func TestAndroid(t *testing.T) {
|
||||
{"DeleteMDMAndroidConfigProfile", testDeleteMDMAndroidConfigProfile},
|
||||
{"GetMDMAndroidProfilesSummary", testMDMAndroidProfilesSummary},
|
||||
{"ListMDMAndroidProfilesToSend", testListMDMAndroidProfilesToSend},
|
||||
{"ListMDMAndroidProfilesToSend_WithExcludeAny", testListMDMAndroidProfilesToSendWithExcludeAny},
|
||||
{"GetMDMAndroidProfilesContents", testGetMDMAndroidProfilesContents},
|
||||
{"BulkUpsertMDMAndroidHostProfiles", testBulkUpsertMDMAndroidHostProfiles},
|
||||
{"BulkUpsertMDMAndroidHostProfiles", testBulkUpsertMDMAndroidHostProfiles2},
|
||||
@@ -1204,7 +1205,7 @@ func testListMDMAndroidProfilesToSend(t *testing.T, ds *Datastore) {
|
||||
// test the exclude any labels condition
|
||||
lblExclAny1, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-1", Query: "select 1"})
|
||||
require.NoError(t, err)
|
||||
lblExclAny2, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-2", Query: "select 1"})
|
||||
lblExclAny2, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-2", LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
p6, err := ds.NewMDMAndroidConfigProfile(ctx, *androidProfileForTest("no-team-6", lblExclAny1, lblExclAny2))
|
||||
require.NoError(t, err)
|
||||
@@ -1348,6 +1349,160 @@ func testListMDMAndroidProfilesToSend(t *testing.T, ds *Datastore) {
|
||||
require.Empty(t, toRemoveProfs)
|
||||
}
|
||||
|
||||
// Specific test for "exclude any" logic which can be tricky because manual
|
||||
// labels apply immediately whereas dynamic labels only apply after label membership
|
||||
// has been determined for the host(as signified by the LabelUpdatedAt timestamp).
|
||||
// Base test covers some of this but it's a good area for extra testing in light of
|
||||
// https://github.com/fleetdm/fleet/issues/33132
|
||||
func testListMDMAndroidProfilesToSendWithExcludeAny(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
// Create some hosts
|
||||
hosts := make([]*fleet.Host, 2)
|
||||
for i := range hosts {
|
||||
androidHost := createAndroidHost(fmt.Sprintf("enterprise-id-%d", i))
|
||||
newHost, err := ds.NewAndroidHost(ctx, androidHost)
|
||||
require.NoError(t, err)
|
||||
hosts[i] = newHost.Host
|
||||
}
|
||||
|
||||
// without any profile, should return empty
|
||||
profs, toRemoveProfs, err := ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, profs)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
|
||||
// Create a team
|
||||
tm, err := ds.NewTeam(ctx, &fleet.Team{Name: "team"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// transfer host 1 to the team
|
||||
err = ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&tm.ID, []uint{hosts[1].ID}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// test the exclude any labels condition
|
||||
lblExclAny1, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-1", Query: "select 1"})
|
||||
require.NoError(t, err)
|
||||
lblExclAny2, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-2", LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Dynamic exclude-any label
|
||||
p1, err := ds.NewMDMAndroidConfigProfile(ctx, *androidProfileForTest("no-team-1", lblExclAny1))
|
||||
require.NoError(t, err)
|
||||
// Manual exclude-any label only
|
||||
p2, err := ds.NewMDMAndroidConfigProfile(ctx, *androidProfileForTest("no-team-2", lblExclAny2))
|
||||
require.NoError(t, err)
|
||||
// Both manual and dynamic label exclusion
|
||||
p3, err := ds.NewMDMAndroidConfigProfile(ctx, *androidProfileForTest("no-team-3", lblExclAny1, lblExclAny2))
|
||||
require.NoError(t, err)
|
||||
|
||||
// p2 becomes immediately applicable because it only has a manual label
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 1)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p2.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p2.Name},
|
||||
}, profs)
|
||||
|
||||
// update the timestamp of when host label membership was updated
|
||||
hosts[0].LabelUpdatedAt = time.Now().UTC().Add(time.Second) // just to be extra safe in tests
|
||||
hosts[0].PolicyUpdatedAt = time.Now().UTC()
|
||||
err = ds.UpdateHost(ctx, hosts[0])
|
||||
require.NoError(t, err)
|
||||
|
||||
// host 0 dynamic labels now apply, and this host is _not_ a member of the excluded labels, so p1, p2 and p3 are now applicable
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 3)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p1.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p1.Name},
|
||||
{ProfileUUID: p2.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p2.Name},
|
||||
{ProfileUUID: p3.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p3.Name},
|
||||
}, profs)
|
||||
|
||||
tmP4 := androidProfileForTest("team-4", lblExclAny1)
|
||||
tmP4.TeamID = &tm.ID
|
||||
tmP5 := androidProfileForTest("team-5", lblExclAny2)
|
||||
tmP5.TeamID = &tm.ID
|
||||
tmP6 := androidProfileForTest("team-6", lblExclAny1, lblExclAny2)
|
||||
tmP6.TeamID = &tm.ID
|
||||
|
||||
// Dynamic exclude-any label
|
||||
p4, err := ds.NewMDMAndroidConfigProfile(ctx, *tmP4)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Manual exclude-any label only
|
||||
p5, err := ds.NewMDMAndroidConfigProfile(ctx, *tmP5)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both manual and dynamic label exclusion
|
||||
p6, err := ds.NewMDMAndroidConfigProfile(ctx, *tmP6)
|
||||
require.NoError(t, err)
|
||||
|
||||
// p5 becomes immediately applicable to host 1 because it only has a manual label
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 4)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p1.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p1.Name},
|
||||
{ProfileUUID: p2.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p2.Name},
|
||||
{ProfileUUID: p3.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p3.Name},
|
||||
{ProfileUUID: p5.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p5.Name},
|
||||
}, profs)
|
||||
|
||||
// Set the hosts label_updated_at causing p4-p6 to become applicable to host 1
|
||||
hosts[1].LabelUpdatedAt = time.Now().UTC().Add(time.Second) // just to be extra safe in tests
|
||||
hosts[1].PolicyUpdatedAt = time.Now().UTC()
|
||||
hosts[1].TeamID = &tm.ID
|
||||
err = ds.UpdateHost(ctx, hosts[1])
|
||||
require.NoError(t, err)
|
||||
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 6)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p1.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p1.Name},
|
||||
{ProfileUUID: p2.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p2.Name},
|
||||
{ProfileUUID: p3.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p3.Name},
|
||||
{ProfileUUID: p4.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p4.Name},
|
||||
{ProfileUUID: p5.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p5.Name},
|
||||
{ProfileUUID: p6.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p6.Name},
|
||||
}, profs)
|
||||
|
||||
// Make host 0 a member of labelExclAny2 which excludes everything except p1 for it
|
||||
_, _, err = ds.UpdateLabelMembershipByHostIDs(ctx, lblExclAny2.ID, []uint{hosts[0].ID}, fleet.TeamFilter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 4)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p1.ProfileUUID, HostUUID: hosts[0].UUID, ProfileName: p1.Name},
|
||||
{ProfileUUID: p4.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p4.Name},
|
||||
{ProfileUUID: p5.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p5.Name},
|
||||
{ProfileUUID: p6.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p6.Name},
|
||||
}, profs)
|
||||
|
||||
// Make hosts 0 and 1 members of labelExclAny1 which excludes everything except p5 for host p1. Android doesn't
|
||||
// currently support dynamic labels but this ensures the datastore processes it right if somehow an Android host
|
||||
// becomes a member of one
|
||||
_, _, err = ds.UpdateLabelMembershipByHostIDs(ctx, lblExclAny1.ID, []uint{hosts[0].ID, hosts[1].ID}, fleet.TeamFilter{})
|
||||
require.NoError(t, err)
|
||||
|
||||
profs, toRemoveProfs, err = ds.ListMDMAndroidProfilesToSend(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, toRemoveProfs)
|
||||
require.Len(t, profs, 1)
|
||||
require.ElementsMatch(t, []*fleet.MDMAndroidProfilePayload{
|
||||
{ProfileUUID: p5.ProfileUUID, HostUUID: hosts[1].UUID, ProfileName: p5.Name},
|
||||
}, profs)
|
||||
}
|
||||
|
||||
func testGetMDMAndroidProfilesContents(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
p1 := androidProfileForTest("p1")
|
||||
|
||||
@@ -3115,8 +3115,13 @@ func generateDesiredStateQuery(entityType string) string {
|
||||
COUNT(mel.label_id) as count_non_broken_labels,
|
||||
COUNT(lm.label_id) as count_host_labels,
|
||||
-- this helps avoid the case where the host is not a member of a label
|
||||
-- just because it hasn't reported results for that label yet.
|
||||
SUM(CASE WHEN lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1 ELSE 0 END) as count_host_updated_after_labels
|
||||
-- just because it hasn't reported results for that label yet. But we
|
||||
-- only need consider this for dynamic labels - manual(type=1) can be
|
||||
-- considered at any time
|
||||
SUM(
|
||||
CASE WHEN lbl.label_membership_type <> 1 AND lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1
|
||||
WHEN lbl.label_membership_type = 1 AND lbl.created_at IS NOT NULL THEN 1
|
||||
ELSE 0 END) as count_host_updated_after_labels
|
||||
FROM
|
||||
${mdmAppleEntityTable} mae
|
||||
JOIN hosts h
|
||||
|
||||
@@ -8900,6 +8900,9 @@ func testGetNanoMDMUserEnrollment(t *testing.T, ds *Datastore) {
|
||||
func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
ctx := t.Context()
|
||||
|
||||
// Explicitly set the labelUpdatedAt very slightly in the past for testing dynamic label logic
|
||||
fiveSecondsAgo := time.Now().Add(-5 * time.Second).UTC().Round(time.Microsecond)
|
||||
|
||||
matchProfiles := func(want, got []*fleet.MDMAppleProfilePayload) {
|
||||
// match only the fields we care about
|
||||
for _, p := range got {
|
||||
@@ -8926,12 +8929,13 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
require.Empty(t, profilesToInstall)
|
||||
|
||||
host1, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-host1-name",
|
||||
OsqueryHostID: ptr.String("1337"),
|
||||
NodeKey: ptr.String("1337"),
|
||||
UUID: "test-uuid-1",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
Hostname: "test-host1-name",
|
||||
OsqueryHostID: ptr.String("1337"),
|
||||
NodeKey: ptr.String("1337"),
|
||||
UUID: "test-uuid-1",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
LabelUpdatedAt: fiveSecondsAgo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// add a user enrollment for this device, nothing else should be modified
|
||||
@@ -8939,24 +8943,26 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
|
||||
// non-macOS hosts shouldn't modify any of the results below
|
||||
_, err = ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-windows-host",
|
||||
OsqueryHostID: ptr.String("4824"),
|
||||
NodeKey: ptr.String("4824"),
|
||||
UUID: "test-windows-host",
|
||||
TeamID: nil,
|
||||
Platform: "windows",
|
||||
Hostname: "test-windows-host",
|
||||
OsqueryHostID: ptr.String("4824"),
|
||||
NodeKey: ptr.String("4824"),
|
||||
UUID: "test-windows-host",
|
||||
TeamID: nil,
|
||||
Platform: "windows",
|
||||
LabelUpdatedAt: fiveSecondsAgo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// a macOS host that's not MDM enrolled into Fleet shouldn't
|
||||
// modify any of the results below
|
||||
_, err = ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-non-mdm-host",
|
||||
OsqueryHostID: ptr.String("4825"),
|
||||
NodeKey: ptr.String("4825"),
|
||||
UUID: "test-non-mdm-host",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
Hostname: "test-non-mdm-host",
|
||||
OsqueryHostID: ptr.String("4825"),
|
||||
NodeKey: ptr.String("4825"),
|
||||
UUID: "test-non-mdm-host",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
LabelUpdatedAt: fiveSecondsAgo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -8969,12 +8975,13 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
}, profilesToInstall)
|
||||
|
||||
hostLabel, err := ds.NewHost(ctx, &fleet.Host{
|
||||
Hostname: "test-host-name-label",
|
||||
OsqueryHostID: ptr.String("1337_label"),
|
||||
NodeKey: ptr.String("1337_label"),
|
||||
UUID: "test-uuid-1-label",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
Hostname: "test-host-name-label",
|
||||
OsqueryHostID: ptr.String("1337_label"),
|
||||
NodeKey: ptr.String("1337_label"),
|
||||
UUID: "test-uuid-1-label",
|
||||
TeamID: nil,
|
||||
Platform: "darwin",
|
||||
LabelUpdatedAt: fiveSecondsAgo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// add a user enrollment for this device, nothing else should be modified
|
||||
@@ -9001,7 +9008,7 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
l6, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-any-6", Query: "select 1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
l7, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-any-7", Query: "select 1"})
|
||||
l7, err := ds.NewLabel(ctx, &fleet.Label{Name: "exclude-any-7", LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
|
||||
profIncludeAny, err := ds.NewMDMAppleConfigProfile(ctx, *configProfileForTest(t, "prof-include-any", "prof-include-any", "prof-include-any", l1, l2, l3), nil)
|
||||
@@ -9010,14 +9017,7 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
profExcludeAny, err := ds.NewMDMAppleConfigProfile(ctx, *configProfileForTest(t, "prof-exclude-any", "prof-exclude-any", "prof-exclude-any", l6, l7), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update hosts' labels updated at timestamp so that the exclude any profile shows up
|
||||
hostLabel.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, hostLabel)
|
||||
require.NoError(t, err)
|
||||
|
||||
host1.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, host1)
|
||||
profExcludeAnyManualOnly, err := ds.NewMDMAppleConfigProfile(ctx, *configProfileForTest(t, "prof-exclude-any-manual", "prof-exclude-any-manual", "prof-exclude-any-manual", l7), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// hostLabel is a member of l1, l4, l5
|
||||
@@ -9026,22 +9026,47 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
|
||||
globalPfs, err = ds.ListMDMAppleConfigProfiles(ctx, ptr.Uint(0))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, globalPfs, 5)
|
||||
require.Len(t, globalPfs, 6)
|
||||
|
||||
// still the same profiles to assign (plus the one for hostLabel) as there are no profiles for team 1
|
||||
profilesToInstall, err = ds.ListMDMAppleProfilesToInstall(ctx, "")
|
||||
|
||||
require.NoError(t, err)
|
||||
matchProfiles([]*fleet.MDMAppleProfilePayload{
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAny.ProfileUUID, ProfileIdentifier: profIncludeAny.Identifier, ProfileName: profIncludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAll.ProfileUUID, ProfileIdentifier: profIncludeAll.Identifier, ProfileName: profIncludeAll.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Update hosts' labels updated at timestamp so that the exclude any profile with a dynamic label shows up
|
||||
hostLabel.LabelUpdatedAt = time.Now().Add(1 * time.Second)
|
||||
err = ds.UpdateHost(ctx, hostLabel)
|
||||
require.NoError(t, err)
|
||||
|
||||
host1.LabelUpdatedAt = time.Now().Add(1 * time.Second)
|
||||
err = ds.UpdateHost(ctx, host1)
|
||||
require.NoError(t, err)
|
||||
|
||||
profilesToInstall, err = ds.ListMDMAppleProfilesToInstall(ctx, "")
|
||||
require.NoError(t, err)
|
||||
matchProfiles([]*fleet.MDMAppleProfilePayload{
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAny.ProfileUUID, ProfileIdentifier: profIncludeAny.Identifier, ProfileName: profIncludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAll.ProfileUUID, ProfileIdentifier: profIncludeAll.Identifier, ProfileName: profIncludeAll.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Remove the l1<->hostLabel relationship, but add l2<->hostLabel. The profile should still show
|
||||
@@ -9059,12 +9084,14 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAny.ProfileUUID, ProfileIdentifier: profIncludeAny.Identifier, ProfileName: profIncludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAll.ProfileUUID, ProfileIdentifier: profIncludeAll.Identifier, ProfileName: profIncludeAll.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Remove the l2<->hostLabel relationship. The profie should no longer show up since it's
|
||||
@@ -9078,11 +9105,13 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profIncludeAll.ProfileUUID, ProfileIdentifier: profIncludeAll.Identifier, ProfileName: profIncludeAll.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Remove the l4<->hostLabel relationship. Since the profile is "include-all", it should no longer show
|
||||
@@ -9096,13 +9125,15 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Add a l6<->host relationship. The exclude-any profile should no longer be assigned to hostLabel.
|
||||
// Add a l6<->host relationship. The dynamic exclude-any profile should no longer be assigned to hostLabel but manual still will
|
||||
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{{l6.ID, hostLabel.ID}})
|
||||
require.NoError(t, err)
|
||||
profilesToInstall, err = ds.ListMDMAppleProfilesToInstall(ctx, "")
|
||||
@@ -9112,9 +9143,11 @@ func testMDMAppleProfileLabels(t *testing.T, ds *Datastore) {
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAny.ProfileUUID, ProfileIdentifier: profExcludeAny.Identifier, ProfileName: profExcludeAny.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: host1.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
|
||||
{ProfileUUID: globProf1.ProfileUUID, ProfileIdentifier: globProf1.Identifier, ProfileName: globProf1.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: globProf2.ProfileUUID, ProfileIdentifier: globProf2.Identifier, ProfileName: globProf2.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
{ProfileUUID: profExcludeAnyManualOnly.ProfileUUID, ProfileIdentifier: profExcludeAnyManualOnly.Identifier, ProfileName: profExcludeAnyManualOnly.Name, HostUUID: hostLabel.UUID, HostPlatform: "darwin", Scope: fleet.PayloadScopeSystem},
|
||||
}, profilesToInstall)
|
||||
}
|
||||
|
||||
|
||||
@@ -1322,8 +1322,13 @@ const windowsMDMProfilesDesiredStateQuery = `
|
||||
COUNT(mcpl.label_id) as count_non_broken_labels,
|
||||
COUNT(lm.label_id) as count_host_labels,
|
||||
-- this helps avoid the case where the host is not a member of a label
|
||||
-- just because it hasn't reported results for that label yet.
|
||||
SUM(CASE WHEN lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1 ELSE 0 END) as count_host_updated_after_labels
|
||||
-- just because it hasn't reported results for that label yet. But we
|
||||
-- only need consider this for dynamic labels - manual(type=1) can be
|
||||
-- considered at any time
|
||||
SUM(
|
||||
CASE WHEN lbl.label_membership_type <> 1 AND lbl.created_at IS NOT NULL AND h.label_updated_at >= lbl.created_at THEN 1
|
||||
WHEN lbl.label_membership_type = 1 AND lbl.created_at IS NOT NULL THEN 1
|
||||
ELSE 0 END) as count_host_updated_after_labels
|
||||
FROM
|
||||
mdm_windows_configuration_profiles mwcp
|
||||
JOIN hosts h
|
||||
|
||||
@@ -2311,7 +2311,8 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
u := uuid.New().String()
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
// Set this slightly in the past to test dynamic label exclusion
|
||||
LabelUpdatedAt: time.Now().Add(-5 * time.Second),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: &u,
|
||||
@@ -2359,7 +2360,7 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// exclude-all labels
|
||||
// exclude-any labels
|
||||
l6, err := ds.NewLabel(ctx, &fleet.Label{
|
||||
Name: "exclude-any-label6",
|
||||
Description: "desc",
|
||||
@@ -2368,9 +2369,9 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
|
||||
l7, err := ds.NewLabel(ctx, &fleet.Label{
|
||||
Name: "exclude-any-label7",
|
||||
Description: "desc",
|
||||
Query: "select 1;",
|
||||
Name: "exclude-any-label7",
|
||||
Description: "desc",
|
||||
LabelMembershipType: fleet.LabelMembershipTypeManual,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2397,25 +2398,31 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
checksum = md5.Sum(includeAllProf.SyncML) // nolint:gosec // used only to hash for efficient comparisons
|
||||
profileChecksums[includeAllProf.ProfileUUID] = checksum[:]
|
||||
|
||||
// Create a profile with "exclude-all" with l6 and l7
|
||||
excludeAllProf, err := ds.NewMDMWindowsConfigProfile(
|
||||
// Create a profile with "exclude-any" with l6 and l7
|
||||
excludeAnyProf, err := ds.NewMDMWindowsConfigProfile(
|
||||
ctx,
|
||||
*windowsConfigProfileForTest(t, "prof-exclude-any", "./Foo/Bar", l6, l7),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
checksum = md5.Sum(excludeAllProf.SyncML) // nolint:gosec // used only to hash for efficient comparisons
|
||||
profileChecksums[excludeAllProf.ProfileUUID] = checksum[:]
|
||||
checksum = md5.Sum(excludeAnyProf.SyncML) // nolint:gosec // used only to hash for efficient comparisons
|
||||
profileChecksums[excludeAnyProf.ProfileUUID] = checksum[:]
|
||||
|
||||
// Create a profile with "exclude-any" with l7 only since it is a manual label
|
||||
excludeAnyManualProf, err := ds.NewMDMWindowsConfigProfile(
|
||||
ctx,
|
||||
*windowsConfigProfileForTest(t, "prof-exclude-any-manual", "./Foo/Bar", l7),
|
||||
nil,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
checksum = md5.Sum(excludeAnyManualProf.SyncML) // nolint:gosec // used only to hash for efficient comparisons
|
||||
profileChecksums[excludeAnyManualProf.ProfileUUID] = checksum[:]
|
||||
|
||||
// Connect the host and l1, l4, l5
|
||||
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{{l1.ID, host.ID}, {l4.ID, host.ID}, {l5.ID, host.ID}})
|
||||
require.NoError(t, err)
|
||||
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We should see all 3 profiles in the "to install" list
|
||||
// We should see 3 profiles in the "to install" list
|
||||
profilesToInstall, err := ds.ListMDMWindowsProfilesToInstall(ctx)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []*fleet.MDMWindowsProfilePayload{
|
||||
@@ -2428,8 +2435,34 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
Checksum: profileChecksums[includeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAllProf.ProfileUUID, ProfileName: excludeAllProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAllProf.ProfileUUID],
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
|
||||
host.LabelUpdatedAt = time.Now().Add(1 * time.Second)
|
||||
err = ds.UpdateHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We should see all 4 profiles in the "to install" list
|
||||
profilesToInstall, err = ds.ListMDMWindowsProfilesToInstall(ctx)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []*fleet.MDMWindowsProfilePayload{
|
||||
{
|
||||
ProfileUUID: includeAllProf.ProfileUUID, ProfileName: includeAllProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[includeAllProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: includeAnyProf.ProfileUUID, ProfileName: includeAnyProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[includeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAnyProf.ProfileUUID, ProfileName: excludeAnyProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
|
||||
@@ -2453,8 +2486,12 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
Checksum: profileChecksums[includeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAllProf.ProfileUUID, ProfileName: excludeAllProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAllProf.ProfileUUID],
|
||||
ProfileUUID: excludeAnyProf.ProfileUUID, ProfileName: excludeAnyProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
|
||||
@@ -2471,8 +2508,12 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
Checksum: profileChecksums[includeAllProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAllProf.ProfileUUID, ProfileName: excludeAllProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAllProf.ProfileUUID],
|
||||
ProfileUUID: excludeAnyProf.ProfileUUID, ProfileName: excludeAnyProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
|
||||
@@ -2485,18 +2526,28 @@ func testMDMWindowsProfileLabels(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []*fleet.MDMWindowsProfilePayload{
|
||||
{
|
||||
ProfileUUID: excludeAllProf.ProfileUUID, ProfileName: excludeAllProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAllProf.ProfileUUID],
|
||||
ProfileUUID: excludeAnyProf.ProfileUUID, ProfileName: excludeAnyProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyProf.ProfileUUID],
|
||||
},
|
||||
{
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
|
||||
// Add a l6<->host relationship. The exclude-any profile should be gone now.
|
||||
// Add a l6<->host relationship. The exclude-any profile with l6 and l7 should be gone now with only
|
||||
// the exclude-any-manual profile remaining.
|
||||
err = ds.AsyncBatchInsertLabelMembership(ctx, [][2]uint{{l6.ID, host.ID}})
|
||||
require.NoError(t, err)
|
||||
|
||||
profilesToInstall, err = ds.ListMDMWindowsProfilesToInstall(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, profilesToInstall)
|
||||
require.ElementsMatch(t, []*fleet.MDMWindowsProfilePayload{
|
||||
{
|
||||
ProfileUUID: excludeAnyManualProf.ProfileUUID, ProfileName: excludeAnyManualProf.Name, HostUUID: host.UUID,
|
||||
Checksum: profileChecksums[excludeAnyManualProf.ProfileUUID],
|
||||
},
|
||||
}, profilesToInstall)
|
||||
}
|
||||
|
||||
func expectWindowsProfiles(
|
||||
|
||||
@@ -4078,7 +4078,10 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
SUM(CASE WHEN lbl.created_at IS NOT NULL AND :host_label_updated_at >= lbl.created_at THEN 1 ELSE 0 END) as count_host_updated_after_labels
|
||||
SUM(
|
||||
CASE WHEN lbl.label_membership_type <> 1 AND lbl.created_at IS NOT NULL AND :host_label_updated_at >= lbl.created_at THEN 1
|
||||
WHEN lbl.label_membership_type = 1 AND lbl.created_at IS NOT NULL THEN 1
|
||||
ELSE 0 END) as count_host_updated_after_labels
|
||||
FROM
|
||||
software_installer_labels sil
|
||||
LEFT OUTER JOIN labels lbl
|
||||
|
||||
@@ -6873,8 +6873,8 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
||||
err = ds.UpdateHost(ctx, anotherHost)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, ds.AddLabelsToHost(ctx, thirdHost.ID, []uint{label1.ID}))
|
||||
anotherHost.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, anotherHost)
|
||||
thirdHost.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, thirdHost)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
@@ -6985,10 +6985,10 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
||||
checkSoftware(software)
|
||||
|
||||
// Add "exclude any" labels to installer2
|
||||
label2, err := ds.NewLabel(ctx, &fleet.Label{Name: "label2" + t.Name()})
|
||||
label2, err := ds.NewLabel(ctx, &fleet.Label{Name: "label2" + t.Name(), Query: "select 1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
label3, err := ds.NewLabel(ctx, &fleet.Label{Name: "label3" + t.Name()})
|
||||
label3, err := ds.NewLabel(ctx, &fleet.Label{Name: "label3" + t.Name(), LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setOrUpdateSoftwareInstallerLabelsDB(ctx, ds.writer(ctx), installerID2, fleet.LabelIdentsWithScope{
|
||||
@@ -7118,6 +7118,46 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
||||
scoped, err = ds.IsSoftwareInstallerLabelScoped(ctx, installerID3, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, scoped)
|
||||
|
||||
// Add yet another installer. No label yet.
|
||||
installer4 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "hello",
|
||||
PreInstallQuery: "SELECT 1",
|
||||
PostInstallScript: "world",
|
||||
UninstallScript: "goodbye",
|
||||
InstallerFile: tfr1,
|
||||
StorageID: "storage5",
|
||||
Filename: "file5",
|
||||
Title: "file5",
|
||||
Version: "3.0",
|
||||
Source: "apps",
|
||||
UserID: user1.ID,
|
||||
BundleIdentifier: "bi5",
|
||||
Platform: "darwin",
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
}
|
||||
installerID4, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, installer4)
|
||||
require.NoError(t, err)
|
||||
|
||||
// No labels yet, so we should see it
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, installer2.Filename, installer3.Filename, installer4.Filename)
|
||||
|
||||
// Create a new manual label and apply it to the new installer
|
||||
label5, err := ds.NewLabel(ctx, &fleet.Label{Name: "label5" + t.Name(), LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setOrUpdateSoftwareInstallerLabelsDB(ctx, ds.writer(ctx), installerID4, fleet.LabelIdentsWithScope{
|
||||
LabelScope: fleet.LabelScopeExcludeAny,
|
||||
ByName: map[string]fleet.LabelIdent{label5.Name: {LabelName: label5.Name, LabelID: label5.ID}},
|
||||
}, softwareTypeInstaller)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Installer4 is still listed and does not need the host's LabelUpdatedAt to be updated
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, installer2.Filename, installer3.Filename, installer4.Filename)
|
||||
}
|
||||
|
||||
func testListHostSoftwareVulnerableAndVPP(t *testing.T, ds *Datastore) {
|
||||
@@ -8069,7 +8109,7 @@ func testListHostSoftwareWithLabelScopingVPP(t *testing.T, ds *Datastore) {
|
||||
}, softwareTypeVPP)
|
||||
require.NoError(t, err)
|
||||
|
||||
// intall vpp app on fourth host
|
||||
// install vpp app on fourth host
|
||||
fourthHostVpp1CmdUUID := createVPPAppInstallRequest(t, ds, fourthHost, vppApp.AdamID, user1)
|
||||
_, err = ds.activateNextUpcomingActivity(ctx, ds.writer(ctx), fourthHost.ID, "")
|
||||
require.NoError(t, err)
|
||||
@@ -8217,7 +8257,6 @@ func testListHostSoftwareWithLabelScopingVPP(t *testing.T, ds *Datastore) {
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
@@ -8226,6 +8265,22 @@ func testListHostSoftwareWithLabelScopingVPP(t *testing.T, ds *Datastore) {
|
||||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
|
||||
// Create a manual label (prior was dynamic) and set it instead as exclude-any for the VPP app
|
||||
label4, err := ds.NewLabel(ctx, &fleet.Label{Name: "label4" + t.Name(), LabelMembershipType: fleet.LabelMembershipTypeManual})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = setOrUpdateSoftwareInstallerLabelsDB(ctx, ds.writer(ctx), vppAppTeamID, fleet.LabelIdentsWithScope{
|
||||
LabelScope: fleet.LabelScopeExcludeAny,
|
||||
ByName: map[string]fleet.LabelIdent{label4.Name: {LabelName: label4.Name, LabelID: label4.ID}},
|
||||
}, softwareTypeVPP)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The host need not update its LabelUpdatedAt because the label is manually scoped and not applied,
|
||||
// so it is immediately resolvable (and the host doesn't have the excluded label applied to it)
|
||||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
}
|
||||
|
||||
func testListHostSoftwareLastOpenedAt(t *testing.T, ds *Datastore) {
|
||||
|
||||
@@ -387,7 +387,9 @@ func (svc *Service) updateHost(ctx context.Context, device *androidmanagement.De
|
||||
host.Host.CPUType = device.HardwareInfo.Hardware
|
||||
host.Host.HardwareModel = svc.getComputerName(device)
|
||||
host.Host.HardwareVendor = device.HardwareInfo.Brand
|
||||
host.LabelUpdatedAt = time.Time{}
|
||||
// Android hosts do not support dynamic labels so we should keep their labelUpdatedAt updated at every
|
||||
// checkin to match platforms that do and make label logic simpler
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
if device.LastStatusReportTime != "" {
|
||||
lastStatusReportTime, err := time.Parse(time.RFC3339, device.LastStatusReportTime)
|
||||
if err != nil {
|
||||
@@ -443,7 +445,7 @@ func (svc *Service) addNewHost(ctx context.Context, device *androidmanagement.De
|
||||
CPUType: device.HardwareInfo.Hardware,
|
||||
HardwareModel: svc.getComputerName(device),
|
||||
HardwareVendor: device.HardwareInfo.Brand,
|
||||
LabelUpdatedAt: time.Time{},
|
||||
LabelUpdatedAt: time.Now(),
|
||||
DetailUpdatedAt: time.Time{},
|
||||
UUID: device.HardwareInfo.EnterpriseSpecificId,
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql/common_mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -589,6 +590,8 @@ func TestUpdateHost(t *testing.T) {
|
||||
// verify UUID is set correctly
|
||||
var capturedHost *fleet.AndroidHost
|
||||
mockDS.UpdateAndroidHostFunc = func(ctx context.Context, host *fleet.AndroidHost, fromEnroll bool) error {
|
||||
// Validate that the update always updates the label updated at value with a recent one
|
||||
require.Greater(t, host.LabelUpdatedAt, time.Now().Add(-5*time.Second))
|
||||
capturedHost = host
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4003,6 +4003,9 @@ func (svc *MDMAppleCheckinAndCommandService) handleRefetchDeviceResults(ctx cont
|
||||
host.PrimaryMac = wifiMac
|
||||
host.HardwareModel = productName
|
||||
host.DetailUpdatedAt = time.Now()
|
||||
// iOS/iPadOS devices do not support dynamic labels at this time so we should update their LabelUpdatedAt timestamp
|
||||
// on refetch similar to other platforms to simplify exclusion logic with dynamic labels
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
host.RefetchRequested = false
|
||||
|
||||
if err := svc.ds.UpdateHost(ctx, host); err != nil {
|
||||
|
||||
@@ -4518,6 +4518,7 @@ func TestMDMCommandAndReportResultsIOSIPadOSRefetch(t *testing.T) {
|
||||
require.Equal(t, "ff:ff:ff:ff:ff:ff", host.PrimaryMac)
|
||||
require.Equal(t, "iPad13,18", host.HardwareModel)
|
||||
require.WithinDuration(t, time.Now(), host.DetailUpdatedAt, 1*time.Minute)
|
||||
require.WithinDuration(t, time.Now(), host.LabelUpdatedAt, 1*time.Minute)
|
||||
return nil
|
||||
}
|
||||
ds.SetOrUpdateHostDisksSpaceFunc = func(ctx context.Context, incomingHostID uint, gigsAvailable, percentAvailable, gigsTotal float64, gigsAll *float64) error {
|
||||
|
||||
Reference in New Issue
Block a user