Profile Labels Include Any CLI (#23434)

#22576


Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
This commit is contained in:
Dante Catalfamo
2024-11-05 15:13:44 -05:00
committed by GitHub
co-authored by Sarah Gillespie
parent d050ddbeb1
commit aa3fd29c13
15 changed files with 441 additions and 20 deletions
+1
View File
@@ -0,0 +1 @@
- Add support for labels_include_any to gitops
+4 -2
View File
@@ -2075,11 +2075,13 @@ func TestGitOpsCustomSettings(t *testing.T) {
}{
{"testdata/gitops/global_macos_windows_custom_settings_valid.yml", ""},
{"testdata/gitops/global_macos_custom_settings_valid_deprecated.yml", ""},
{"testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included`},
{"testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included`},
{"testdata/gitops/global_windows_custom_settings_invalid_label_mix_2.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included`},
{"testdata/gitops/global_windows_custom_settings_unknown_label.yml", `some or all the labels provided don't exist`},
{"testdata/gitops/team_macos_windows_custom_settings_valid.yml", ""},
{"testdata/gitops/team_macos_custom_settings_valid_deprecated.yml", ""},
{"testdata/gitops/team_macos_windows_custom_settings_invalid_labels_mix.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`},
{"testdata/gitops/team_macos_windows_custom_settings_invalid_labels_mix.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included`},
{"testdata/gitops/team_macos_windows_custom_settings_invalid_labels_mix_2.yml", `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included`},
{"testdata/gitops/team_macos_windows_custom_settings_unknown_label.yml", `some or all the labels provided don't exist`},
}
for _, c := range cases {
@@ -0,0 +1,96 @@
controls:
windows_settings:
custom_settings:
- path: ./lib/windows-screenlock.xml
labels_include_all:
- B
labels_include_any:
- C
scripts:
enable_disk_encryption: false
macos_migration:
enable: false
mode: ""
webhook_url: ""
macos_setup:
bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
windows_enabled_and_configured: true
windows_updates:
deadline_days: null
grace_period_days: null
queries:
policies:
agent_options:
command_line_flags:
distributed_denylist_duration: 0
config:
options:
disable_distributed: false
distributed_interval: 10
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/v1/osquery/log
pack_delimiter: /
org_settings:
server_settings:
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_report_cap: 2000
query_reports_disabled: false
scripts_disabled: false
server_url: $FLEET_SERVER_URL
ai_features_disabled: true
org_info:
contact_url: https://fleetdm.com/company/contact
org_logo_url: ""
org_logo_url_light_background: ""
org_name: $ORG_NAME
smtp_settings:
authentication_method: authmethod_plain
authentication_type: authtype_username_password
configured: false
domain: ""
enable_smtp: false
enable_ssl_tls: true
enable_start_tls: true
password: ""
port: 587
sender_address: ""
server: ""
user_name: ""
verify_ssl_certs: true
sso_settings:
enable_jit_provisioning: false
enable_jit_role_sync: false
enable_sso: true
enable_sso_idp_login: false
entity_id: https://saml.example.com/entityid
idp_image_url: ""
idp_name: MockSAML
issuer_uri: ""
metadata: ""
metadata_url: https://mocksaml.com/api/saml/metadata
integrations:
mdm:
webhook_settings:
fleet_desktop:
transparency_url: https://fleetdm.com/transparency
host_expiry_settings:
host_expiry_enabled: false
activity_expiry_settings:
activity_expiry_enabled: true
activity_expiry_window: 60
features:
enable_host_users: true
enable_software_inventory: true
vulnerability_settings:
databases_path: ""
secrets:
- secret: ABC
software:
@@ -0,0 +1,29 @@
name: "${TEST_TEAM_NAME}"
team_settings:
secrets:
- secret: "ABC"
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: true
host_expiry_window: 30
agent_options:
controls:
macos_settings:
custom_settings:
- path: ./lib/macos-password.mobileconfig
labels_include_any:
- A
labels:
- B
windows_settings:
custom_settings:
- path: ./lib/windows-screenlock.xml
labels_include_any:
- A
labels_exclude_any:
- C
policies:
queries:
software:
+2 -2
View File
@@ -1435,14 +1435,14 @@ func (svc *Service) editTeamFromSpec(
func validateTeamCustomSettings(invalid *fleet.InvalidArgumentError, prefix string, customSettings []fleet.MDMProfileSpec) {
for i, prof := range customSettings {
count := 0
for _, b := range []bool{len(prof.Labels) > 0, len(prof.LabelsIncludeAll) > 0, len(prof.LabelsExcludeAny) > 0} {
for _, b := range []bool{len(prof.Labels) > 0, len(prof.LabelsIncludeAll) > 0, len(prof.LabelsIncludeAny) > 0, len(prof.LabelsExcludeAny) > 0} {
if b {
count++
}
}
if count > 1 {
invalid.Append(fmt.Sprintf("%s_settings.custom_settings", prefix),
fmt.Sprintf(`Couldn't edit %s_settings.custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`, prefix))
fmt.Sprintf(`Couldn't edit %s_settings.custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`, prefix))
}
if len(prof.Labels) > 0 {
customSettings[i].LabelsIncludeAll = customSettings[i].Labels
+5 -1
View File
@@ -218,7 +218,11 @@ type MDMProfilesUpdates struct {
// profiles and labels.
//
// NOTE: json representation of the fields is a bit awkward to match the
// required API response, as this struct is returned within profile responses.
// required API response, as this struct is returned within profile
// responses.
//
// NOTE The fields in this struct other than LabelName and LabelID
// MAY NOT BE SET CORRECTLY, dependong on where they're being ingested from.
type ConfigurationProfileLabel struct {
ProfileUUID string `db:"profile_uuid" json:"-"`
LabelName string `db:"label_name" json:"name"`
+34 -2
View File
@@ -419,6 +419,7 @@ type MDMConfigProfilePayload struct {
CreatedAt time.Time `json:"created_at" db:"created_at"`
UploadedAt time.Time `json:"updated_at" db:"uploaded_at"` // NOTE: JSON field is still `updated_at` for historical reasons, would be an API breaking change
LabelsIncludeAll []ConfigurationProfileLabel `json:"labels_include_all,omitempty" db:"-"`
LabelsIncludeAny []ConfigurationProfileLabel `json:"labels_include_any,omitempty" db:"-"`
LabelsExcludeAny []ConfigurationProfileLabel `json:"labels_exclude_any,omitempty" db:"-"`
}
@@ -432,6 +433,7 @@ type MDMProfileBatchPayload struct {
// LabelsIncludeAll.
Labels []string `json:"labels,omitempty"`
LabelsIncludeAll []string `json:"labels_include_all,omitempty"`
LabelsIncludeAny []string `json:"labels_include_any,omitempty"`
LabelsExcludeAny []string `json:"labels_exclude_any,omitempty"`
}
@@ -448,6 +450,7 @@ func NewMDMConfigProfilePayloadFromWindows(cp *MDMWindowsConfigProfile) *MDMConf
CreatedAt: cp.CreatedAt,
UploadedAt: cp.UploadedAt,
LabelsIncludeAll: cp.LabelsIncludeAll,
LabelsIncludeAny: cp.LabelsIncludeAny,
LabelsExcludeAny: cp.LabelsExcludeAny,
}
}
@@ -467,6 +470,7 @@ func NewMDMConfigProfilePayloadFromApple(cp *MDMAppleConfigProfile) *MDMConfigPr
CreatedAt: cp.CreatedAt,
UploadedAt: cp.UploadedAt,
LabelsIncludeAll: cp.LabelsIncludeAll,
LabelsIncludeAny: cp.LabelsIncludeAny,
LabelsExcludeAny: cp.LabelsExcludeAny,
}
}
@@ -486,6 +490,7 @@ func NewMDMConfigProfilePayloadFromAppleDDM(decl *MDMAppleDeclaration) *MDMConfi
CreatedAt: decl.CreatedAt,
UploadedAt: decl.UploadedAt,
LabelsIncludeAll: decl.LabelsIncludeAll,
LabelsIncludeAny: decl.LabelsIncludeAny,
LabelsExcludeAny: decl.LabelsExcludeAny,
}
}
@@ -504,6 +509,10 @@ type MDMProfileSpec struct {
// of in order to receive the profile. It must be a member of all listed
// labels.
LabelsIncludeAll []string `json:"labels_include_all,omitempty"`
// LabelsIncludeAny is a list of label names that the host must be a member
// of in order to receive the profile. It may be a member of
// any listed labels.
LabelsIncludeAny []string `json:"labels_include_any,omitempty"`
// LabelsExcludeAll is a list of label names that the host must not be a
// member of in order to receive the profile. It must not be a member of any
// of the listed labels.
@@ -558,6 +567,10 @@ func (p *MDMProfileSpec) Copy() *MDMProfileSpec {
clone.LabelsIncludeAll = make([]string, len(p.LabelsIncludeAll))
copy(clone.LabelsIncludeAll, p.LabelsIncludeAll)
}
if len(p.LabelsIncludeAny) > 0 {
clone.LabelsIncludeAny = make([]string, len(p.LabelsIncludeAny))
copy(clone.LabelsIncludeAny, p.LabelsIncludeAny)
}
if len(p.LabelsExcludeAny) > 0 {
clone.LabelsExcludeAny = make([]string, len(p.LabelsExcludeAny))
copy(clone.LabelsExcludeAny, p.LabelsExcludeAny)
@@ -591,6 +604,10 @@ func MDMProfileSpecsMatch(a, b []MDMProfileSpec) bool {
pathLabelIncludeCounts[v.Path] = labelCountMap(v.Labels)
}
}
pathLabelsIncludeAnyCounts := make(map[string]map[string]int)
for _, v := range a {
pathLabelsIncludeAnyCounts[v.Path] = labelCountMap(v.LabelsIncludeAny)
}
pathLabelExcludeCounts := make(map[string]map[string]int)
for _, v := range a {
pathLabelExcludeCounts[v.Path] = labelCountMap(v.LabelsExcludeAny)
@@ -598,8 +615,9 @@ func MDMProfileSpecsMatch(a, b []MDMProfileSpec) bool {
for _, v := range b {
includeLabels, okIncl := pathLabelIncludeCounts[v.Path]
includeAnyLabels, okInclAny := pathLabelsIncludeAnyCounts[v.Path]
excludeLabels, okExcl := pathLabelExcludeCounts[v.Path]
if !okIncl || !okExcl {
if !okIncl || !okExcl || !okInclAny {
return false
}
@@ -621,6 +639,19 @@ func MDMProfileSpecsMatch(a, b []MDMProfileSpec) bool {
}
}
bLabelIncludeAnyCounts := labelCountMap(v.LabelsIncludeAny)
for label, count := range bLabelIncludeAnyCounts {
if includeAnyLabels[label] != count {
return false
}
includeAnyLabels[label] -= count
}
for _, count := range includeAnyLabels {
if count != 0 {
return false
}
}
bLabelExcludeCounts := labelCountMap(v.LabelsExcludeAny)
for label, count := range bLabelExcludeCounts {
if excludeLabels[label] != count {
@@ -635,10 +666,11 @@ func MDMProfileSpecsMatch(a, b []MDMProfileSpec) bool {
}
delete(pathLabelIncludeCounts, v.Path)
delete(pathLabelsIncludeAnyCounts, v.Path)
delete(pathLabelExcludeCounts, v.Path)
}
return len(pathLabelIncludeCounts) == 0 && len(pathLabelExcludeCounts) == 0
return len(pathLabelIncludeCounts) == 0 && len(pathLabelsIncludeAnyCounts) == 0 && len(pathLabelExcludeCounts) == 0
}
type MDMLabelsMode string
+7 -2
View File
@@ -956,14 +956,19 @@ func (svc *Service) validateMDM(
checkCustomSettings := func(prefix string, customSettings []fleet.MDMProfileSpec) {
for i, prof := range customSettings {
count := 0
for _, b := range []bool{len(prof.Labels) > 0, len(prof.LabelsIncludeAll) > 0, len(prof.LabelsExcludeAny) > 0} {
for _, b := range []bool{
len(prof.Labels) > 0,
len(prof.LabelsIncludeAll) > 0,
len(prof.LabelsIncludeAny) > 0,
len(prof.LabelsExcludeAny) > 0,
} {
if b {
count++
}
}
if count > 1 {
invalid.Append(fmt.Sprintf("%s_settings.custom_settings", prefix),
fmt.Sprintf(`Couldn't edit %s_settings.custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`, prefix))
fmt.Sprintf(`Couldn't edit %s_settings.custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`, prefix))
}
if len(prof.Labels) > 0 {
customSettings[i].LabelsIncludeAll = customSettings[i].Labels
+2
View File
@@ -381,6 +381,7 @@ func getProfilesContents(baseDir string, macProfiles []fleet.MDMProfileSpec, win
Contents: fileContents,
Labels: profile.Labels,
LabelsIncludeAll: profile.LabelsIncludeAll,
LabelsIncludeAny: profile.LabelsIncludeAny,
LabelsExcludeAny: profile.LabelsExcludeAny,
})
@@ -1086,6 +1087,7 @@ func extractAppCfgCustomSettings(appCfg interface{}, platformKey string) []fleet
// validations are done later on in the Fleet API endpoint.
profSpec.Labels = extractLabelField(m, "labels")
profSpec.LabelsIncludeAll = extractLabelField(m, "labels_include_all")
profSpec.LabelsIncludeAny = extractLabelField(m, "labels_include_any")
profSpec.LabelsExcludeAny = extractLabelField(m, "labels_exclude_any")
if profSpec.Path != "" {
@@ -1789,7 +1789,7 @@ func (s *integrationMDMTestSuite) TestAppConfigMDMCustomSettings() {
}
}`), http.StatusUnprocessableEntity)
msg := extractServerErrorText(res.Body)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
@@ -1801,7 +1801,31 @@ func (s *integrationMDMTestSuite) TestAppConfigMDMCustomSettings() {
}
}`), http.StatusUnprocessableEntity)
msg = extractServerErrorText(res.Body)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
"windows_settings": {
"custom_settings": [
{"path": "foo", "labels_include_any": ["a"], "labels_exclude_any": ["b"]}
]
}
}
}`), http.StatusUnprocessableEntity)
msg = extractServerErrorText(res.Body)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
"windows_settings": {
"custom_settings": [
{"path": "foo", "labels": ["a"], "labels_include_any": ["b"]}
]
}
}
}`), http.StatusUnprocessableEntity)
msg = extractServerErrorText(res.Body)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
}
func (s *integrationMDMTestSuite) TestApplyTeamsMDMAppleProfiles() {
@@ -1920,7 +1944,7 @@ func (s *integrationMDMTestSuite) TestApplyTeamsMDMAppleProfiles() {
}}}
res = s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
assert.Contains(t, errMsg, `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
assert.Contains(t, errMsg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
}
func (s *integrationMDMTestSuite) TestBatchSetMDMAppleProfiles() {
@@ -3781,7 +3805,7 @@ func (s *integrationMDMTestSuite) TestApplyTeamsMDMWindowsProfiles() {
}
`), http.StatusUnprocessableEntity)
errMsg := extractServerErrorText(res.Body)
assert.Contains(t, errMsg, `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
assert.Contains(t, errMsg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
}
func (s *integrationMDMTestSuite) TestBatchSetMDMProfiles() {
@@ -3982,7 +4006,7 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMProfiles() {
{Name: "N1", Contents: mobileconfigForTest("N1", "I1"), Labels: []string{lbl1.Name}, LabelsExcludeAny: []string{lbl2.Name}},
}}, http.StatusUnprocessableEntity)
msg = extractServerErrorText(res.Body)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
require.Contains(t, msg, `For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
// successful batch-set
s.Do("POST", "/api/v1/fleet/mdm/profiles/batch", batchSetMDMProfilesRequest{Profiles: []fleet.MDMProfileBatchPayload{
+56 -6
View File
@@ -1602,6 +1602,7 @@ func (svc *Service) BatchSetMDMProfiles(
profiles[i].Labels = nil
}
labels = append(labels, profiles[i].LabelsIncludeAll...)
labels = append(labels, profiles[i].LabelsIncludeAny...)
labels = append(labels, profiles[i].LabelsExcludeAny...)
}
labelMap, err := svc.batchValidateProfileLabels(ctx, labels)
@@ -1853,12 +1854,22 @@ func getAppleProfiles(
mdmDecl := fleet.NewMDMAppleDeclaration(prof.Contents, tmID, prof.Name, rawDecl.Type, rawDecl.Identifier)
for _, labelName := range prof.LabelsIncludeAll {
if lbl, ok := labelMap[labelName]; ok {
declLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
RequireAll: true,
}
mdmDecl.LabelsIncludeAll = append(mdmDecl.LabelsIncludeAll, declLabel)
}
}
for _, labelName := range prof.LabelsIncludeAny {
if lbl, ok := labelMap[labelName]; ok {
declLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
}
mdmDecl.LabelsIncludeAll = append(mdmDecl.LabelsIncludeAll, declLabel)
mdmDecl.LabelsIncludeAny = append(mdmDecl.LabelsIncludeAny, declLabel)
}
}
for _, labelName := range prof.LabelsExcludeAny {
@@ -1905,12 +1916,31 @@ func getAppleProfiles(
for _, labelName := range prof.LabelsIncludeAll {
if lbl, ok := labelMap[labelName]; ok {
mdmProf.LabelsIncludeAll = append(mdmProf.LabelsIncludeAll, lbl)
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
RequireAll: true,
}
mdmProf.LabelsIncludeAll = append(mdmProf.LabelsIncludeAll, mdmLabel)
}
}
for _, labelName := range prof.LabelsIncludeAny {
if lbl, ok := labelMap[labelName]; ok {
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
}
mdmProf.LabelsIncludeAny = append(mdmProf.LabelsIncludeAny, mdmLabel)
}
}
for _, labelName := range prof.LabelsExcludeAny {
if lbl, ok := labelMap[labelName]; ok {
mdmProf.LabelsExcludeAny = append(mdmProf.LabelsExcludeAny, lbl)
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
Exclude: true,
}
mdmProf.LabelsExcludeAny = append(mdmProf.LabelsExcludeAny, mdmLabel)
}
}
@@ -1981,12 +2011,31 @@ func getWindowsProfiles(
}
for _, labelName := range profile.LabelsIncludeAll {
if lbl, ok := labelMap[labelName]; ok {
mdmProf.LabelsIncludeAll = append(mdmProf.LabelsIncludeAll, lbl)
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
RequireAll: true,
}
mdmProf.LabelsIncludeAll = append(mdmProf.LabelsIncludeAll, mdmLabel)
}
}
for _, labelName := range profile.LabelsIncludeAny {
if lbl, ok := labelMap[labelName]; ok {
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
}
mdmProf.LabelsIncludeAny = append(mdmProf.LabelsIncludeAny, mdmLabel)
}
}
for _, labelName := range profile.LabelsExcludeAny {
if lbl, ok := labelMap[labelName]; ok {
mdmProf.LabelsExcludeAny = append(mdmProf.LabelsExcludeAny, lbl)
mdmLabel := fleet.ConfigurationProfileLabel{
LabelName: lbl.LabelName,
LabelID: lbl.LabelID,
Exclude: true,
}
mdmProf.LabelsExcludeAny = append(mdmProf.LabelsExcludeAny, mdmLabel)
}
}
@@ -2020,6 +2069,7 @@ func validateProfiles(profiles []fleet.MDMProfileBatchPayload) error {
var count int
for _, b := range []bool{
len(profile.LabelsIncludeAll) > 0,
len(profile.LabelsIncludeAny) > 0,
len(profile.LabelsExcludeAny) > 0,
len(profile.Labels) > 0,
} {
@@ -2028,7 +2078,7 @@ func validateProfiles(profiles []fleet.MDMProfileBatchPayload) error {
}
}
if count > 1 {
return fleet.NewInvalidArgumentError("mdm", `Couldn't edit custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all" or "labels" can be included.`)
return fleet.NewInvalidArgumentError("mdm", `Couldn't edit custom_settings. For each profile, only one of "labels_exclude_any", "labels_include_all", "labels_include_any" or "labels" can be included.`)
}
if len(profile.Contents) > 1024*1024 {
+173
View File
@@ -20,6 +20,7 @@ import (
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/config"
@@ -1963,3 +1964,175 @@ func TestMDMResendConfigProfileAuthz(t *testing.T) {
})
}
}
func TestBatchSetMDMProfilesLabels(t *testing.T) {
ds := new(mock.Store)
// while the config profiles are not premium-only, teams are and we want to test with teams.
license := &fleet.LicenseInfo{Tier: fleet.TierPremium}
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{License: license, SkipCreateTestUsers: true})
_ = ctx
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{
MDM: fleet.MDM{
EnabledAndConfigured: true,
WindowsEnabledAndConfigured: true,
},
}, nil
}
ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) {
return &fleet.Team{
ID: tid,
Name: "team1",
}, nil
}
type ProfileLabels struct {
IncludeAll bool
IncludeAny bool
ExcludeAny bool
}
profileLabels := map[string]*ProfileLabels{}
ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDeclarations []*fleet.MDMAppleDeclaration) (updates fleet.MDMProfilesUpdates, err error) {
for _, profile := range macProfiles {
profileLabels[profile.Name] = &ProfileLabels{}
if len(profile.LabelsIncludeAll) > 0 {
assert.True(t, profile.LabelsIncludeAll[0].RequireAll, "profile label missing RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAll[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAll = true
}
if len(profile.LabelsIncludeAny) > 0 {
assert.False(t, profile.LabelsIncludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAny[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAny = true
}
if len(profile.LabelsExcludeAny) > 0 {
assert.False(t, profile.LabelsExcludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.True(t, profile.LabelsExcludeAny[0].Exclude, "profile label should have Exclude: %s", profile.Name)
profileLabels[profile.Name].ExcludeAny = true
}
}
for _, profile := range winProfiles {
profileLabels[profile.Name] = &ProfileLabels{}
if len(profile.LabelsIncludeAll) > 0 {
assert.True(t, profile.LabelsIncludeAll[0].RequireAll, "profile label missing RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAll[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAll = true
}
if len(profile.LabelsIncludeAny) > 0 {
assert.False(t, profile.LabelsIncludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAny[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAny = true
}
if len(profile.LabelsExcludeAny) > 0 {
assert.False(t, profile.LabelsExcludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.True(t, profile.LabelsExcludeAny[0].Exclude, "profile label should have Exclude: %s", profile.Name)
profileLabels[profile.Name].ExcludeAny = true
}
}
for _, profile := range macDeclarations {
profileLabels[profile.Name] = &ProfileLabels{}
if len(profile.LabelsIncludeAll) > 0 {
assert.True(t, profile.LabelsIncludeAll[0].RequireAll, "profile label missing RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAll[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAll = true
}
if len(profile.LabelsIncludeAny) > 0 {
assert.False(t, profile.LabelsIncludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.False(t, profile.LabelsIncludeAny[0].Exclude, "profile label shouldn't have Exclude: %s", profile.Name)
profileLabels[profile.Name].IncludeAny = true
}
if len(profile.LabelsExcludeAny) > 0 {
assert.False(t, profile.LabelsExcludeAny[0].RequireAll, "profile label shouldn't have RequireAll: %s", profile.Name)
assert.True(t, profile.LabelsExcludeAny[0].Exclude, "profile label should have Exclude: %s", profile.Name)
profileLabels[profile.Name].ExcludeAny = true
}
}
return fleet.MDMProfilesUpdates{}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) (updates fleet.MDMProfilesUpdates, err error) {
return fleet.MDMProfilesUpdates{}, nil
}
var labelID uint
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
m := map[string]uint{}
for _, label := range labels {
labelID++
m[label] = labelID
}
return m, nil
}
profiles := []fleet.MDMProfileBatchPayload{
// macOS
{
Name: "MIncAll",
Contents: mobileconfigForTest("MIncAll", "1"),
LabelsIncludeAll: []string{"a", "b"},
},
{
Name: "MIncAny",
Contents: mobileconfigForTest("MIncAny", "2"),
LabelsIncludeAny: []string{"a", "b"},
},
{
Name: "MExclAny",
Contents: mobileconfigForTest("MExclAny", "3"),
LabelsExcludeAny: []string{"a", "b"},
},
// Windows
{
Name: "WIncAll",
Contents: syncMLForTest("./Foo/Bar"),
LabelsIncludeAll: []string{"a", "b"},
},
{
Name: "WIncAny",
Contents: syncMLForTest("./Foo/Barz"),
LabelsIncludeAny: []string{"a", "b"},
},
{
Name: "WExclAny",
Contents: syncMLForTest("./Foo/Barf"),
LabelsExcludeAny: []string{"a", "b"},
},
// Declarative
{
Name: "DIncAll",
Contents: declarationForTest("DIncAll"),
LabelsIncludeAll: []string{"a", "b"},
},
{
Name: "DIncAny",
Contents: declarationForTest("DIncAny"),
LabelsIncludeAny: []string{"a", "b"},
},
{
Name: "DExclAny",
Contents: declarationForTest("DExclAny"),
LabelsExcludeAny: []string{"a", "b"},
},
}
authCtx := test.UserContext(ctx, test.UserAdmin)
err := svc.BatchSetMDMProfiles(authCtx, ptr.Uint(1), nil, profiles, false, false, ptr.Bool(true))
require.NoError(t, err)
assert.Equal(t, ProfileLabels{IncludeAll: true}, *profileLabels["MIncAll"])
assert.Equal(t, ProfileLabels{IncludeAny: true}, *profileLabels["MIncAny"])
assert.Equal(t, ProfileLabels{ExcludeAny: true}, *profileLabels["MExclAny"])
assert.Equal(t, ProfileLabels{IncludeAll: true}, *profileLabels["WIncAll"])
assert.Equal(t, ProfileLabels{IncludeAny: true}, *profileLabels["WIncAny"])
assert.Equal(t, ProfileLabels{ExcludeAny: true}, *profileLabels["WExclAny"])
assert.Equal(t, ProfileLabels{IncludeAll: true}, *profileLabels["DIncAll"])
assert.Equal(t, ProfileLabels{IncludeAny: true}, *profileLabels["DIncAny"])
assert.Equal(t, ProfileLabels{ExcludeAny: true}, *profileLabels["DExclAny"])
}
@@ -136,6 +136,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MacOSSettings CustomSettings []fleet.MD
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Path string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Labels []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAll []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAny []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsExcludeAny []string
github.com/fleetdm/fleet/v4/server/fleet/MacOSSettings DeprecatedEnableDiskEncryption *bool
github.com/fleetdm/fleet/v4/server/fleet/MDM MacOSSetup fleet.MacOSSetup
@@ -1,4 +1,5 @@
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Path string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Labels []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAll []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAny []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsExcludeAny []string
@@ -18,6 +18,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MacOSSettings CustomSettings []fleet.MD
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Path string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec Labels []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAll []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsIncludeAny []string
github.com/fleetdm/fleet/v4/server/fleet/MDMProfileSpec LabelsExcludeAny []string
github.com/fleetdm/fleet/v4/server/fleet/MacOSSettings DeprecatedEnableDiskEncryption *bool
github.com/fleetdm/fleet/v4/server/fleet/TeamMDM MacOSSetup fleet.MacOSSetup