Profile Labels Include Any CLI (#23434)
#22576 Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
This commit is contained in:
co-authored by
Sarah Gillespie
parent
d050ddbeb1
commit
aa3fd29c13
@@ -0,0 +1 @@
|
||||
- Add support for labels_include_any to gitops
|
||||
@@ -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 {
|
||||
|
||||
+96
@@ -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:
|
||||
+29
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user