Add disk_encryption option to config and team YAML (#10185)

This commit is contained in:
Martin Angers
2023-02-28 15:34:46 -05:00
committed by GitHub
parent ac3541659d
commit 4593c49ec4
25 changed files with 1274 additions and 730 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ CREATE TABLE `app_config_json` (
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"macos_updates\": {\"deadline\": \"\", \"minimum_version\": \"\"}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enabled_and_configured\": false}, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"org_logo_url\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"macos_settings\": {\"custom_settings\": null}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"deferred_save_host\": false, \"live_query_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"macos_updates\": {\"deadline\": \"\", \"minimum_version\": \"\"}, \"macos_settings\": {\"custom_settings\": null, \"enable_disk_encryption\": false}, \"apple_bm_default_team\": \"\", \"apple_bm_terms_expired\": false, \"enabled_and_configured\": false}, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"org_logo_url\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"deferred_save_host\": false, \"live_query_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}}','2020-01-01 01:01:01','2020-01-01 01:01:01');
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `carve_blocks` (
+47 -19
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/url"
"reflect"
"regexp"
"sort"
"time"
@@ -118,7 +119,8 @@ type MDM struct {
// the server starts.
EnabledAndConfigured bool `json:"enabled_and_configured"`
MacOSUpdates MacOSUpdates `json:"macos_updates"`
MacOSUpdates MacOSUpdates `json:"macos_updates"`
MacOSSettings MacOSSettings `json:"macos_settings"`
/////////////////////////////////////////////////////////////////
// WARNING: If you add to this struct make sure it's taken into
@@ -166,35 +168,64 @@ func (m MacOSUpdates) Validate() error {
// MacOSSettings contains settings specific to macOS.
type MacOSSettings struct {
CustomSettings []string `json:"custom_settings"`
CustomSettings []string `json:"custom_settings"`
EnableDiskEncryption bool `json:"enable_disk_encryption"`
// NOTE: make sure to update the ToMap/FromMap methods when adding/updating fields.
}
func (s MacOSSettings) ToMap() map[string]interface{} {
return map[string]interface{}{
"custom_settings": s.CustomSettings,
"custom_settings": s.CustomSettings,
"enable_disk_encryption": s.EnableDiskEncryption,
}
}
// CustomSettingsFromMap sets the custom settings field from the provided map,
// which is the map type from the ApplyTeams spec struct. It returns true if
// the custom settings were set from the map, false if they were left
// unchanged.
func (s *MacOSSettings) CustomSettingsFromMap(m map[string]interface{}) bool {
// FromMap sets the macOS settings from the provided map, which is the map type
// from the ApplyTeams spec struct. It returns a map of fields that were set in
// the map (ie. the key was present even if empty) or an error. If the
// operation updates an existing team, it should be called on the existing
// MacOSSettings so that its fields are replaced only if present in the map.
func (s *MacOSSettings) FromMap(m map[string]interface{}) (map[string]bool, error) {
set := make(map[string]bool)
if v, ok := m["custom_settings"]; ok {
set["custom_settings"] = true
vals, ok := v.([]interface{})
if v == nil || ok {
strs := make([]string, 0, len(vals))
for _, v := range vals {
s, ok := v.(string)
if ok && s != "" {
strs = append(strs, s)
str, ok := v.(string)
if !ok {
// error, must be a []string
return nil, &json.UnmarshalTypeError{
Value: fmt.Sprintf("%T", v),
Type: reflect.TypeOf(s.CustomSettings),
Field: "macos_settings.custom_settings",
}
}
strs = append(strs, str)
}
s.CustomSettings = strs
return true
}
}
return false
if v, ok := m["enable_disk_encryption"]; ok {
set["enable_disk_encryption"] = true
b, ok := v.(bool)
if !ok {
// error, must be a bool
return nil, &json.UnmarshalTypeError{
Value: fmt.Sprintf("%T", v),
Type: reflect.TypeOf(s.EnableDiskEncryption),
Field: "macos_settings.enable_disk_encryption",
}
}
s.EnableDiskEncryption = b
}
return set, nil
}
// AppConfig holds server configuration that can be changed via the API.
@@ -224,8 +255,6 @@ type AppConfig struct {
MDM MDM `json:"mdm"`
MacOSSettings MacOSSettings `json:"macos_settings"`
// when true, strictDecoding causes the UnmarshalJSON method to return an
// error if there are unknown fields in the raw JSON.
strictDecoding bool
@@ -284,7 +313,6 @@ func (c *AppConfig) Copy() *AppConfig {
// SSOSettings: nothing needs cloning
// FleetDesktop: nothing needs cloning
// VulnerabilitySettings: nothing needs cloning
// MDM: nothing needs cloning
if c.WebhookSettings.FailingPoliciesWebhook.PolicyIDs != nil {
clone.WebhookSettings.FailingPoliciesWebhook.PolicyIDs = make([]uint, len(c.WebhookSettings.FailingPoliciesWebhook.PolicyIDs))
@@ -305,9 +333,9 @@ func (c *AppConfig) Copy() *AppConfig {
}
}
if c.MacOSSettings.CustomSettings != nil {
clone.MacOSSettings.CustomSettings = make([]string, len(c.MacOSSettings.CustomSettings))
copy(clone.MacOSSettings.CustomSettings, c.MacOSSettings.CustomSettings)
if c.MDM.MacOSSettings.CustomSettings != nil {
clone.MDM.MacOSSettings.CustomSettings = make([]string, len(c.MDM.MacOSSettings.CustomSettings))
copy(clone.MDM.MacOSSettings.CustomSettings, c.MDM.MacOSSettings.CustomSettings)
}
return &clone
+24 -15
View File
@@ -125,7 +125,6 @@ type TeamConfig struct {
Integrations TeamIntegrations `json:"integrations"`
Features Features `json:"features"`
MDM TeamMDM `json:"mdm"`
MacOSSettings MacOSSettings `json:"macos_settings"`
}
type TeamWebhookSettings struct {
@@ -133,7 +132,22 @@ type TeamWebhookSettings struct {
}
type TeamMDM struct {
MacOSUpdates MacOSUpdates `json:"macos_updates"`
MacOSSettings MacOSSettings `json:"macos_settings"`
// NOTE: TeamSpecMDM must be kept in sync with TeamMDM.
}
type TeamSpecMDM struct {
MacOSUpdates MacOSUpdates `json:"macos_updates"`
// A map is used for the macos settings so that we can easily detect if its
// sub-keys were provided or not in an "apply" call. E.g. if the
// custom_settings key is specified but empty, then we need to clear the
// value, but if it isn't provided, we need to leave the existing value
// unmodified.
MacOSSettings map[string]interface{} `json:"macos_settings"`
// NOTE: TeamMDM must be kept in sync with TeamSpecMDM.
}
// Scan implements the sql.Scanner interface
@@ -271,14 +285,7 @@ type TeamSpec struct {
Secrets []EnrollSecret `json:"secrets,omitempty"`
Features *json.RawMessage `json:"features"`
MDM TeamMDM `json:"mdm"`
// A map is used for the macos settings so that we can easily detect if its
// sub-keys were provided or not in an "apply" call. E.g. if the
// custom_settings key is specified but empty, then we need to clear the
// value, but if it isn't provided, we need to leave the existing value
// unmodified.
MacOSSettings map[string]interface{} `json:"macos_settings"`
MDM TeamSpecMDM `json:"mdm"`
}
// TeamSpecFromTeam returns a TeamSpec constructed from the given Team.
@@ -300,12 +307,14 @@ func TeamSpecFromTeam(t *Team) (*TeamSpec, error) {
agentOptions = *t.Config.AgentOptions
}
var mdmSpec TeamSpecMDM
mdmSpec.MacOSUpdates = t.Config.MDM.MacOSUpdates
mdmSpec.MacOSSettings = t.Config.MDM.MacOSSettings.ToMap()
return &TeamSpec{
Name: t.Name,
AgentOptions: agentOptions,
Features: &featuresJSON,
Secrets: secrets,
MDM: t.Config.MDM,
MacOSSettings: t.Config.MacOSSettings.ToMap(),
Name: t.Name,
AgentOptions: agentOptions,
Features: &featuresJSON,
Secrets: secrets,
MDM: mdmSpec,
}, nil
}
+21 -19
View File
@@ -137,7 +137,6 @@ func getAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet.Se
WebhookSettings: config.WebhookSettings,
Integrations: config.Integrations,
MDM: config.MDM,
MacOSSettings: config.MacOSSettings,
},
appConfigResponseFields: appConfigResponseFields{
UpdateInterval: updateIntervalConfig,
@@ -248,10 +247,6 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
}
oldAppConfig := appConfig.Copy()
// keep this original values, as they cannot be modified via this request.
origAppleBMTerms := oldAppConfig.MDM.AppleBMTermsExpired
origMDMEnabled := oldAppConfig.MDM.EnabledAndConfigured
license, err := svc.License(ctx)
if err != nil {
return nil, err
@@ -273,24 +268,12 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
return nil, ctxerr.Wrap(ctx, err, "modify AppConfig")
}
// TODO(mna): this ports the validations from the old validationMiddleware
// correctly, but this could be optimized so that we don't unmarshal the
// incoming bytes twice.
invalid := &fleet.InvalidArgumentError{}
var newAppConfig fleet.AppConfig
if err := json.Unmarshal(p, &newAppConfig); err != nil {
return nil, ctxerr.Wrap(ctx, &fleet.BadRequestError{Message: err.Error()})
}
if len(newAppConfig.MacOSSettings.CustomSettings) != 0 {
if !svc.config.MDMApple.Enable {
// TODO(mna): eventually we should detect the minimum config required for
// this to be allowed, probably just SCEP/APNs?
invalid.Append("macos_settings.custom_settings", "cannot set custom settings: Fleet MDM is not enabled")
return nil, ctxerr.Wrap(ctx, invalid)
}
}
if newAppConfig.FleetDesktop.TransparencyURL != "" {
if license.Tier != "premium" {
invalid.Append("transparency_url", ErrMissingLicense.Error())
@@ -358,8 +341,8 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
// payload we don't return an error in this case because it would
// prevent using the output of fleetctl get config as input to fleetctl
// apply or this endpoint.
appConfig.MDM.AppleBMTermsExpired = origAppleBMTerms
appConfig.MDM.EnabledAndConfigured = origMDMEnabled
appConfig.MDM.AppleBMTermsExpired = oldAppConfig.MDM.AppleBMTermsExpired
appConfig.MDM.EnabledAndConfigured = oldAppConfig.MDM.EnabledAndConfigured
// do not send a test email in dry-run mode, so this is a good place to stop
// (we also delete the removed integrations after that, which we don't want
@@ -470,6 +453,25 @@ func (svc *Service) validateMDM(
mdm *fleet.MDM,
invalid *fleet.InvalidArgumentError,
) {
if mdm.MacOSSettings.EnableDiskEncryption && !license.IsPremium() {
invalid.Append("macos_settings.enable_disk_encryption", ErrMissingLicense.Error())
}
if !svc.config.MDMApple.Enable {
// TODO(mna): eventually we should detect the minimum config required for
// this to be allowed, probably just SCEP/APNs?
if len(mdm.MacOSSettings.CustomSettings) != 0 {
invalid.Append("macos_settings.custom_settings",
`Couldn't update macos_settings because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
if mdm.MacOSSettings.EnableDiskEncryption {
invalid.Append("macos_settings.enable_disk_encryption",
`Couldn't update macos_settings because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
}
if name := mdm.AppleBMDefaultTeam; name != "" && name != oldMdm.AppleBMDefaultTeam {
if !license.IsPremium() {
invalid.Append("mdm.apple_bm_default_team", ErrMissingLicense.Error())
+13 -7
View File
@@ -393,7 +393,11 @@ func extractAppCfgMacOSCustomSettings(appCfg interface{}) []string {
if !ok {
return nil
}
mos, ok := asMap["macos_settings"].(map[string]interface{})
mmdm, ok := asMap["mdm"].(map[string]interface{})
if !ok {
return nil
}
mos, ok := mmdm["macos_settings"].(map[string]interface{})
if !ok || mos == nil {
return nil
}
@@ -426,21 +430,23 @@ func extractTmSpecsMacOSCustomSettings(tmSpecs []json.RawMessage) map[string][]s
var m map[string][]string
for _, tm := range tmSpecs {
var spec struct {
Name string `json:"name"`
MacOSSettings struct {
CustomSettings json.RawMessage `json:"custom_settings"`
} `json:"macos_settings"`
Name string `json:"name"`
MDM struct {
MacOSSettings struct {
CustomSettings json.RawMessage `json:"custom_settings"`
} `json:"macos_settings"`
} `json:"mdm"`
}
if err := json.Unmarshal(tm, &spec); err != nil {
// ignore, this will fail in the call to apply team specs
continue
}
if spec.Name != "" && len(spec.MacOSSettings.CustomSettings) > 0 {
if spec.Name != "" && len(spec.MDM.MacOSSettings.CustomSettings) > 0 {
if m == nil {
m = make(map[string][]string)
}
var cs []string
if err := json.Unmarshal(spec.MacOSSettings.CustomSettings, &cs); err != nil {
if err := json.Unmarshal(spec.MDM.MacOSSettings.CustomSettings, &cs); err != nil {
// ignore, will fail in apply team specs call
continue
}
+39 -29
View File
@@ -30,7 +30,8 @@ kind: config
spec:
org_info:
org_name: "Fleet"
macos_settings:
mdm:
macos_settings:
`,
nil,
},
@@ -42,8 +43,9 @@ kind: config
spec:
org_info:
org_name: "Fleet"
macos_settings:
custom_settings:
mdm:
macos_settings:
custom_settings:
`,
[]string{},
},
@@ -55,10 +57,11 @@ kind: config
spec:
org_info:
org_name: "Fleet"
macos_settings:
custom_settings:
- "a"
- "b"
mdm:
macos_settings:
custom_settings:
- "a"
- "b"
`,
[]string{"a", "b"},
},
@@ -70,12 +73,13 @@ kind: config
spec:
org_info:
org_name: "Fleet"
macos_settings:
custom_settings:
- "a"
- ""
- 4
- "c"
mdm:
macos_settings:
custom_settings:
- "a"
- ""
- 4
- "c"
`,
[]string{"a", "c"},
},
@@ -116,14 +120,16 @@ kind: team
spec:
team:
name: Fleet
macos_settings:
mdm:
macos_settings:
---
apiVersion: v1
kind: team
spec:
team:
name: Fleet2
macos_settings:
mdm:
macos_settings:
`,
nil,
},
@@ -135,16 +141,18 @@ kind: team
spec:
team:
name: "Fleet"
macos_settings:
custom_settings:
mdm:
macos_settings:
custom_settings:
---
apiVersion: v1
kind: team
spec:
team:
name: "Fleet2"
macos_settings:
custom_settings:
mdm:
macos_settings:
custom_settings:
`,
map[string][]string{"Fleet": {}, "Fleet2": {}},
},
@@ -156,10 +164,11 @@ kind: team
spec:
team:
name: "Fleet"
macos_settings:
custom_settings:
- "a"
- "b"
mdm:
macos_settings:
custom_settings:
- "a"
- "b"
`,
map[string][]string{"Fleet": {"a", "b"}},
},
@@ -171,12 +180,13 @@ kind: team
spec:
team:
name: "Fleet"
macos_settings:
custom_settings:
- "a"
- ""
- 42
- "c"
mdm:
macos_settings:
custom_settings:
- "a"
- ""
- 42
- "c"
`,
map[string][]string{},
},
+9 -2
View File
@@ -4814,10 +4814,17 @@ func (s *integrationTestSuite) TestAppConfig() {
// set the macos custom settings fields, fails due to MDM not configured
res := s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"macos_settings": { "custom_settings": ["foo", "bar"] }
"mdm": { "macos_settings": { "custom_settings": ["foo", "bar"] } }
}`), http.StatusUnprocessableEntity)
errMsg := extractServerErrorText(res.Body)
assert.Contains(t, errMsg, "Fleet MDM is not enabled")
assert.Contains(t, errMsg, "Couldn't update macos_settings because MDM features aren't turned on in Fleet.")
// set the macos disk encryption field, fails due to license
res = s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "macos_settings": { "enable_disk_encryption": true } }
}`), http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
assert.Contains(t, errMsg, "missing or invalid license")
// try to set the apple bm default team, which is premium only
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
+21 -5
View File
@@ -80,7 +80,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
// updates a team, no secret is provided so it will keep the one generated
// automatically when the team was created.
agentOpts := json.RawMessage(`{"config": {"views": {"foo": "bar"}}, "overrides": {"platforms": {"darwin": {"views": {"bar": "qux"}}}}}`)
mdm := fleet.TeamMDM{
mdm := fleet.TeamSpecMDM{
MacOSUpdates: fleet.MacOSUpdates{
MinimumVersion: "10.15.0",
Deadline: "2021-01-01",
@@ -112,7 +112,12 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
EnableSoftwareInventory: false,
AdditionalQueries: ptr.RawMessage(json.RawMessage(`{"foo": "bar"}`)),
}, team.Config.Features)
require.Equal(t, mdm, team.Config.MDM)
require.Equal(t, fleet.TeamMDM{
MacOSUpdates: fleet.MacOSUpdates{
MinimumVersion: "10.15.0",
Deadline: "2021-01-01",
},
}, team.Config.MDM)
// an activity was created for team spec applied
s.lastActivityMatches(fleet.ActivityTypeAppliedSpecTeam{}.ActivityName(), fmt.Sprintf(`{"teams": [{"id": %d, "name": %q}]}`, team.ID, team.Name), 0)
@@ -141,10 +146,20 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
// dry-run with valid agent options and custom macos settings
agentOpts = json.RawMessage(`{"config": {"views": {"foo": "qux"}}}`)
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: teamName, AgentOptions: agentOpts, MacOSSettings: map[string]interface{}{"custom_settings": []string{"foo", "bar"}}}}}
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: teamName, AgentOptions: agentOpts, MDM: fleet.TeamSpecMDM{MacOSSettings: map[string]interface{}{"custom_settings": []string{"foo", "bar"}}}}}}
res = s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusUnprocessableEntity, "dry_run", "true")
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Fleet MDM is not enabled")
require.Contains(t, errMsg, "Couldn't update macos_settings because MDM features aren't turned on in Fleet.")
// dry-run with macos disk encryption set to false, no error
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: teamName, MDM: fleet.TeamSpecMDM{MacOSSettings: map[string]interface{}{"enable_disk_encryption": false}}}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK, "dry_run", "true")
// dry-run with macos disk encryption set to true
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: teamName, MDM: fleet.TeamSpecMDM{MacOSSettings: map[string]interface{}{"enable_disk_encryption": true}}}}}
res = s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusUnprocessableEntity, "dry_run", "true")
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Couldn't update macos_settings because MDM features aren't turned on in Fleet.")
// dry-run with valid agent options only
agentOpts = json.RawMessage(`{"config": {"views": {"foo": "qux"}}}`)
@@ -154,7 +169,8 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
team, err = s.ds.TeamByName(context.Background(), teamName)
require.NoError(t, err)
require.Contains(t, string(*team.Config.AgentOptions), `"foo": "bar"`) // unchanged
require.Empty(t, team.Config.MacOSSettings.CustomSettings) // unchanged
require.Empty(t, team.Config.MDM.MacOSSettings.CustomSettings) // unchanged
require.False(t, team.Config.MDM.MacOSSettings.EnableDiskEncryption) // unchanged
// apply without agent options specified
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: teamName}}}
+170 -26
View File
@@ -1114,36 +1114,78 @@ func (s *integrationMDMTestSuite) TestAppConfigMDMAppleProfiles() {
// set the macos custom settings fields
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"macos_settings": { "custom_settings": ["foo", "bar"] }
"mdm": { "macos_settings": { "custom_settings": ["foo", "bar"] } }
}`), http.StatusOK, &acResp)
assert.Equal(t, []string{"foo", "bar"}, acResp.MacOSSettings.CustomSettings)
assert.Equal(t, []string{"foo", "bar"}, acResp.MDM.MacOSSettings.CustomSettings)
// check that they are returned by a GET /config
acResp = appConfigResponse{}
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
assert.Equal(t, []string{"foo", "bar"}, acResp.MacOSSettings.CustomSettings)
assert.Equal(t, []string{"foo", "bar"}, acResp.MDM.MacOSSettings.CustomSettings)
// patch without specifying the macos custom settings fields, should not remove them
// patch without specifying the macos custom settings fields and an unrelated
// field, should not remove them
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"macos_settings": {}
"mdm": { "macos_settings": {"enable_disk_encryption": true} }
}`), http.StatusOK, &acResp)
assert.Equal(t, []string{"foo", "bar"}, acResp.MacOSSettings.CustomSettings)
assert.Equal(t, []string{"foo", "bar"}, acResp.MDM.MacOSSettings.CustomSettings)
// patch with explicitly empty macos custom settings fields, would remove
// them but this is a dry-run
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"macos_settings": {"custom_settings": null}
"mdm": { "macos_settings": { "custom_settings": null } }
}`), http.StatusOK, &acResp, "dry_run", "true")
assert.Equal(t, []string{"foo", "bar"}, acResp.MacOSSettings.CustomSettings)
assert.Equal(t, []string{"foo", "bar"}, acResp.MDM.MacOSSettings.CustomSettings)
// patch with explicitly empty macos custom settings fields, removes them
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"macos_settings": {"custom_settings": null}
"mdm": { "macos_settings": { "custom_settings": null } }
}`), http.StatusOK, &acResp)
assert.Empty(t, acResp.MacOSSettings.CustomSettings)
assert.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
}
func (s *integrationMDMTestSuite) TestAppConfigMDMAppleDiskEncryption() {
t := s.T()
// set the macos disk encryption field
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "macos_settings": { "enable_disk_encryption": true } }
}`), http.StatusOK, &acResp)
assert.True(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
// check that they are returned by a GET /config
acResp = appConfigResponse{}
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
assert.True(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
// patch without specifying the macos disk encryption and an unrelated field,
// should not alter it
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "macos_settings": {"custom_settings": ["a"]} }
}`), http.StatusOK, &acResp)
assert.True(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
assert.Equal(t, []string{"a"}, acResp.MDM.MacOSSettings.CustomSettings)
// patch with false, would reset it but this is a dry-run
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "macos_settings": { "enable_disk_encryption": false } }
}`), http.StatusOK, &acResp, "dry_run", "true")
assert.True(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
assert.Equal(t, []string{"a"}, acResp.MDM.MacOSSettings.CustomSettings)
// patch with false, resets it
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "macos_settings": { "enable_disk_encryption": false, "custom_settings": ["b"] } }
}`), http.StatusOK, &acResp)
assert.False(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
assert.Equal(t, []string{"b"}, acResp.MDM.MacOSSettings.CustomSettings)
}
func (s *integrationMDMTestSuite) TestApplyTeamsMDMAppleProfiles() {
@@ -1162,53 +1204,155 @@ func (s *integrationMDMTestSuite) TestApplyTeamsMDMAppleProfiles() {
// apply with custom macos settings
teamSpecs := applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MacOSSettings: map[string]interface{}{"custom_settings": []string{"foo", "bar"}},
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"custom_settings": []string{"foo", "bar"}},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
// retrieving the team returns the custom macos settings
var teamResp getTeamResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MacOSSettings.CustomSettings)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MDM.MacOSSettings.CustomSettings)
// apply with invalid macos settings subfield should fail
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MacOSSettings: map[string]interface{}{"foo_bar": 123},
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"foo_bar": 123},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusBadRequest)
res := s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusBadRequest)
errMsg := extractServerErrorText(res.Body)
assert.Contains(t, errMsg, `unsupported key provided: "foo_bar"`)
// apply without custom macos settings specified, should not replace existing settings
// apply with some good and some bad macos settings subfield should fail
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MacOSSettings: map[string]interface{}{},
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"custom_settings": []interface{}{"A", true}},
},
}}}
res = s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusBadRequest)
errMsg = extractServerErrorText(res.Body)
assert.Contains(t, errMsg, `invalid value type at 'macos_settings.custom_settings': expected array of strings but got bool`)
// apply without custom macos settings specified and unrelated field, should
// not replace existing settings
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"enable_disk_encryption": false},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MacOSSettings.CustomSettings)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MDM.MacOSSettings.CustomSettings)
// apply with explicitly empty custom macos settings would clear the existing
// settings, but dry-run
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MacOSSettings: map[string]interface{}{"custom_settings": []string{}},
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"custom_settings": []string{}},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK, "dry_run", "true")
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MacOSSettings.CustomSettings)
require.Equal(t, []string{"foo", "bar"}, teamResp.Team.Config.MDM.MacOSSettings.CustomSettings)
// apply with explicitly empty custom macos settings clears the existing settings
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MacOSSettings: map[string]interface{}{"custom_settings": []string{}},
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"custom_settings": []string{}},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.Equal(t, []string{}, teamResp.Team.Config.MacOSSettings.CustomSettings)
require.Equal(t, []string{}, teamResp.Team.Config.MDM.MacOSSettings.CustomSettings)
}
func (s *integrationMDMTestSuite) TestApplyTeamsMDMAppleDiskEncryption() {
t := s.T()
// create a team through the service so it initializes the agent ops
teamName := t.Name() + "team1"
team := &fleet.Team{
Name: teamName,
Description: "desc team1",
}
var createTeamResp teamResponse
s.DoJSON("POST", "/api/latest/fleet/teams", team, http.StatusOK, &createTeamResp)
require.NotZero(t, createTeamResp.Team.ID)
team = createTeamResp.Team
// apply with disk encryption
teamSpecs := applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"enable_disk_encryption": true},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
// retrieving the team returns the disk encryption setting
var teamResp getTeamResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.True(t, teamResp.Team.Config.MDM.MacOSSettings.EnableDiskEncryption)
// apply with invalid disk encryption value should fail
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"enable_disk_encryption": 123},
},
}}}
res := s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusBadRequest)
errMsg := extractServerErrorText(res.Body)
assert.Contains(t, errMsg, `invalid value type at 'macos_settings.enable_disk_encryption': expected bool but got float64`)
// apply without disk encryption settings specified and unrelated field,
// should not replace existing disk encryption
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"custom_settings": []string{"a"}},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.True(t, teamResp.Team.Config.MDM.MacOSSettings.EnableDiskEncryption)
require.Equal(t, []string{"a"}, teamResp.Team.Config.MDM.MacOSSettings.CustomSettings)
// apply with false would clear the existing setting, but dry-run
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"enable_disk_encryption": false},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK, "dry_run", "true")
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.True(t, teamResp.Team.Config.MDM.MacOSSettings.EnableDiskEncryption)
// apply with false clears the existing setting
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: teamName,
MDM: fleet.TeamSpecMDM{
MacOSSettings: map[string]interface{}{"enable_disk_encryption": false},
},
}}}
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
teamResp = getTeamResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &teamResp)
require.False(t, teamResp.Team.Config.MDM.MacOSSettings.EnableDiskEncryption)
}
func (s *integrationMDMTestSuite) TestBatchSetMDMAppleProfiles() {
+2 -2
View File
@@ -193,7 +193,7 @@ func (req *applyTeamSpecsRequest) DecodeBody(ctx context.Context, r io.Reader) e
// the MacOSSettings field must be validated separately, since it
// JSON-decodes into a free-form map.
for _, spec := range req.Specs {
if spec == nil || spec.MacOSSettings == nil {
if spec == nil || spec.MDM.MacOSSettings == nil {
continue
}
@@ -201,7 +201,7 @@ func (req *applyTeamSpecsRequest) DecodeBody(ctx context.Context, r io.Reader) e
validMap := macOSSettings.ToMap()
// the keys provided must be valid
for k := range spec.MacOSSettings {
for k := range spec.MDM.MacOSSettings {
if _, ok := validMap[k]; !ok {
return ctxerr.Wrap(ctx, fleet.NewUserMessageError(
fmt.Errorf("json: unknown field %q", k),