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()