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:
Jordan Montgomery
2025-11-12 10:16:01 -05:00
committed by GitHub
co-authored by Ian Littman
parent 01108a4d5a
commit 5679052134
13 changed files with 397 additions and 75 deletions
@@ -0,0 +1 @@
* Fixed an issue where "Exclude Any" label scoping did work properly for iOS, iPadOS and Android hosts
+7 -2
View File
@@ -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
+156 -1
View File
@@ -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")
+7 -2
View File
@@ -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
+68 -35
View File
@@ -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)
}
+7 -2
View File
@@ -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
+75 -24
View File
@@ -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(
+4 -1
View File
@@ -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
+61 -6
View File
@@ -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) {
+4 -2
View File
@@ -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
}
+3
View File
@@ -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 {
+1
View File
@@ -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 {