From e023f84914d2e78a79e20e905292df269bb60473 Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Thu, 7 Sep 2023 15:13:32 -0300 Subject: [PATCH] fall back to read fleetd config using the output of `profiles` (#13800) in #12086 we tried to implement a more efficient way to read values from configuration profiles, but we have found that sometimes the wrong value is reported. This seems to be related to an internal caching mechanism, as the issue is fixed if you add/remove a profile. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --- orbit/pkg/profiles/profiles_darwin.go | 68 ++++++++---- orbit/pkg/profiles/profiles_darwin_test.go | 122 ++++++++++++--------- orbit/pkg/update/notifications.go | 2 +- 3 files changed, 119 insertions(+), 73 deletions(-) diff --git a/orbit/pkg/profiles/profiles_darwin.go b/orbit/pkg/profiles/profiles_darwin.go index 0cd3c6ffdc..e140a6f4ea 100644 --- a/orbit/pkg/profiles/profiles_darwin.go +++ b/orbit/pkg/profiles/profiles_darwin.go @@ -4,7 +4,6 @@ package profiles import ( "bytes" - "encoding/json" "errors" "fmt" "net/url" @@ -13,39 +12,64 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" + "github.com/groob/plist" ) -// GetFleetdConfig reads a system level setting set with Fleet's payload identifier. -func GetFleetdConfig() (*fleet.MDMAppleFleetdConfig, error) { - readFleetdConfigAppleScript := fmt.Sprintf(` - const config = $.NSUserDefaults.alloc.initWithSuiteName("%s"); - const enrollSecret = config.objectForKey("EnrollSecret"); - const fleetURL = config.objectForKey("FleetURL"); - const enableScripts = config.objectForKey("EnableScripts"); - JSON.stringify({ - EnrollSecret: ObjC.deepUnwrap(enrollSecret), - FleetURL: ObjC.deepUnwrap(fleetURL), - EnableScripts: ObjC.deepUnwrap(enableScripts), - }); - `, mobileconfig.FleetdConfigPayloadIdentifier) +type profileItem struct { + PayloadContent fleet.MDMAppleFleetdConfig + PayloadType string + PayloadIdentifier string +} - outBuf, err := execScript(readFleetdConfigAppleScript) +type profilePayload struct { + ProfileItems []profileItem +} + +type profilesOutput struct { + ComputerLevel []profilePayload `plist:"_computerlevel"` +} + +// GetFleetdConfig searches and parses a device level configuration profile +// with Fleet's payload identifier. +func GetFleetdConfig() (*fleet.MDMAppleFleetdConfig, error) { + p, err := getProfile(mobileconfig.FleetdConfigPayloadIdentifier) + if err != nil { + if err == ErrNotFound { + return &fleet.MDMAppleFleetdConfig{}, nil + } + + return nil, err + } + + return &p.ProfileItems[0].PayloadContent, nil +} + +func getProfile(identifier string) (*profilePayload, error) { + outBuf, err := execProfileCmd() if err != nil { return nil, fmt.Errorf("get profile: %w", err) } - var cfg fleet.MDMAppleFleetdConfig - if err = json.Unmarshal(outBuf.Bytes(), &cfg); err != nil { - return nil, fmt.Errorf("unmarshaling configuration: %w", err) + var profiles profilesOutput + if err := plist.Unmarshal(outBuf.Bytes(), &profiles); err != nil { + return nil, fmt.Errorf("get profile: %w", err) } - return &cfg, err + for _, profile := range profiles.ComputerLevel { + for _, item := range profile.ProfileItems { + if item.PayloadIdentifier == identifier { + return &profile, nil + } + } + } + + return nil, ErrNotFound } -// execScript is declared as a variable so it can be overwritten by tests. -var execScript = func(script string) (*bytes.Buffer, error) { +// execProfileCmd is declared as a variable so it can be overwritten by tests. +var execProfileCmd = func() (*bytes.Buffer, error) { var outBuf bytes.Buffer - cmd := exec.Command("osascript", "-l", "JavaScript", "-e", script) + cmd := exec.Command("/usr/bin/profiles", "list", "-o", "stdout-xml") cmd.Stdout = &outBuf cmd.Stderr = &outBuf if err := cmd.Run(); err != nil { diff --git a/orbit/pkg/profiles/profiles_darwin_test.go b/orbit/pkg/profiles/profiles_darwin_test.go index 4e7176816a..8919c42270 100644 --- a/orbit/pkg/profiles/profiles_darwin_test.go +++ b/orbit/pkg/profiles/profiles_darwin_test.go @@ -5,6 +5,7 @@ package profiles import ( "bytes" "errors" + "io" "testing" "github.com/fleetdm/fleet/v4/server/fleet" @@ -18,55 +19,18 @@ func TestGetFleetdConfig(t *testing.T) { cmdOut *string cmdErr error wantOut *fleet.MDMAppleFleetdConfig - wantErr string + wantErr error }{ - {nil, testErr, nil, testErr.Error()}, - {ptr.String("invalid-json"), nil, nil, "unmarshaling configuration"}, - {ptr.String("{}"), nil, &fleet.MDMAppleFleetdConfig{}, ""}, - { - ptr.String(`{"EnrollSecret": "ENROLL_SECRET", "FleetURL": "https://test.example.com", "EnableScripts": true}`), - nil, - &fleet.MDMAppleFleetdConfig{ - EnrollSecret: "ENROLL_SECRET", - FleetURL: "https://test.example.com", - EnableScripts: true, - }, - "", - }, - { - ptr.String(`{"EnrollSecret": "ENROLL_SECRET", "FleetURL": "https://test.example.com", "EnableScripts": false}`), - nil, - &fleet.MDMAppleFleetdConfig{ - EnrollSecret: "ENROLL_SECRET", - FleetURL: "https://test.example.com", - EnableScripts: false, - }, - "", - }, - { - ptr.String(`{"EnableScripts": true}`), - nil, - &fleet.MDMAppleFleetdConfig{EnableScripts: true}, - "", - }, - { - ptr.String(`{"EnrollSecret": "ENROLL_SECRET", "FleetURL": ""}`), - nil, - &fleet.MDMAppleFleetdConfig{EnrollSecret: "ENROLL_SECRET"}, - "", - }, - { - ptr.String(`{"EnrollSecret": "", "FleetURL": "https://test.example.com"}`), - nil, - &fleet.MDMAppleFleetdConfig{FleetURL: "https://test.example.com"}, - "", - }, + {nil, testErr, nil, testErr}, + {ptr.String("invalid-xml"), nil, nil, io.EOF}, + {&emptyOutput, nil, &fleet.MDMAppleFleetdConfig{}, nil}, + {&withFleetdConfig, nil, &fleet.MDMAppleFleetdConfig{EnrollSecret: "ENROLL_SECRET", FleetURL: "https://test.example.com"}, nil}, } - origExecScript := execScript - t.Cleanup(func() { execScript = origExecScript }) + origExecProfileCmd := execProfileCmd + t.Cleanup(func() { execProfileCmd = origExecProfileCmd }) for _, c := range cases { - execScript = func(script string) (*bytes.Buffer, error) { + execProfileCmd = func() (*bytes.Buffer, error) { if c.cmdOut == nil { return nil, c.cmdErr } @@ -77,15 +41,73 @@ func TestGetFleetdConfig(t *testing.T) { } out, err := GetFleetdConfig() - if c.wantErr != "" { - require.ErrorContains(t, err, c.wantErr) - } else { - require.NoError(t, err) - } + require.ErrorIs(t, err, c.wantErr) require.Equal(t, c.wantOut, out) } + } +var ( + emptyOutput = ` + + + +` + + withFleetdConfig = ` + + + + + _computerlevel + + + ProfileDescription + test descripiton + ProfileDisplayName + test name + ProfileIdentifier + com.fleetdm.fleetd.config + ProfileInstallDate + 2023-02-27 18:55:07 +0000 + ProfileItems + + + PayloadContent + + EnrollSecret + ENROLL_SECRET + FleetURL + https://test.example.com + + PayloadDescription + test description + PayloadDisplayName + test name + PayloadIdentifier + com.fleetdm.fleetd.config + PayloadType + com.fleetdm.fleetd + PayloadUUID + 0C6AFB45-01B6-4E19-944A-123CD16381C7 + PayloadVersion + 1 + + + ProfileRemovalDisallowed + true + ProfileType + Configuration + ProfileUUID + 8D0F62E6-E24F-4B2F-AFA8-CAC1F07F4FDC + ProfileVersion + 1 + + + +` +) + func TestIsEnrolledInMDM(t *testing.T) { cases := []struct { cmdOut *string diff --git a/orbit/pkg/update/notifications.go b/orbit/pkg/update/notifications.go index 7d02fb7a17..18076ba922 100644 --- a/orbit/pkg/update/notifications.go +++ b/orbit/pkg/update/notifications.go @@ -318,7 +318,7 @@ func ApplyRunScriptsConfigFetcherMiddleware(fetcher OrbitConfigFetcher, scriptsE Fetcher: fetcher, ScriptsExecutionEnabled: scriptsEnabled, ScriptsClient: scriptsClient, - dynamicScriptsEnabledCheckInterval: time.Minute, + dynamicScriptsEnabledCheckInterval: 5 * time.Minute, } // start the dynamic check for scripts enabled if required scriptsFetcher.runDynamicScriptsEnabledCheck()