diff --git a/orbit/changes/11692-profile-config-read b/orbit/changes/11692-profile-config-read new file mode 100644 index 0000000000..28fa771571 --- /dev/null +++ b/orbit/changes/11692-profile-config-read @@ -0,0 +1 @@ +* Improve the logic to read enroll secrets from macOS configuration profiles to be compatible with different MDM providers. diff --git a/orbit/pkg/profiles/profiles_darwin.go b/orbit/pkg/profiles/profiles_darwin.go index 700778719d..feba9230d7 100644 --- a/orbit/pkg/profiles/profiles_darwin.go +++ b/orbit/pkg/profiles/profiles_darwin.go @@ -4,66 +4,49 @@ package profiles import ( "bytes" + "encoding/json" "fmt" "os/exec" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" - "github.com/groob/plist" ) -type profileItem struct { - PayloadContent fleet.MDMAppleFleetdConfig - PayloadType string -} - -type profilePayload struct { - ProfileIdentifier string - ProfileItems []profileItem -} - -type profilesOutput struct { - ComputerLevel []profilePayload `plist:"_computerlevel"` -} - -// GetFleetdConfig searches and parses a device level configuration profile -// with Fleet's payload identifier. +// GetFleetdConfig reads a system level setting set with Fleet's payload identifier. func GetFleetdConfig() (*fleet.MDMAppleFleetdConfig, error) { - p, err := getProfile(mobileconfig.FleetdConfigPayloadIdentifier) - if err != nil { - return nil, err - } + readFleetdConfigAppleScript := fmt.Sprintf(` + const config = $.NSUserDefaults.alloc.initWithSuiteName("%s"); + const enrollSecret = config.objectForKey("EnrollSecret"); + const fleetURL = config.objectForKey("FleetURL"); + JSON.stringify({ + EnrollSecret: ObjC.deepUnwrap(enrollSecret), + FleetURL: ObjC.deepUnwrap(fleetURL), + }); + `, mobileconfig.FleetdConfigPayloadIdentifier) - return &p.ProfileItems[0].PayloadContent, nil -} - -func getProfile(identifier string) (*profilePayload, error) { - outBuf, err := execProfileCmd() + outBuf, err := execScript(readFleetdConfigAppleScript) if err != nil { return nil, fmt.Errorf("get profile: %w", err) } - var profiles profilesOutput - if err := plist.Unmarshal(outBuf.Bytes(), &profiles); 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) } - for _, profile := range profiles.ComputerLevel { - if profile.ProfileIdentifier == identifier { - return &profile, nil - } + if cfg.EnrollSecret == "" || cfg.FleetURL == "" { + return nil, ErrNotFound } - return nil, ErrNotFound + return &cfg, err } -// execProfileCmd is declared as a variable so it can be overwritten by tests. -var execProfileCmd = func() (*bytes.Buffer, error) { +// execScript is declared as a variable so it can be overwritten by tests. +var execScript = func(script string) (*bytes.Buffer, error) { var outBuf bytes.Buffer - cmd := exec.Command("/usr/bin/profiles", "list", "-o", "stdout-xml") + cmd := exec.Command("osascript", "-l", "JavaScript", "-e", script) cmd.Stdout = &outBuf cmd.Stderr = &outBuf - if err := cmd.Run(); err != nil { return nil, err } diff --git a/orbit/pkg/profiles/profiles_darwin_test.go b/orbit/pkg/profiles/profiles_darwin_test.go index 1a657b5a61..da53214106 100644 --- a/orbit/pkg/profiles/profiles_darwin_test.go +++ b/orbit/pkg/profiles/profiles_darwin_test.go @@ -5,7 +5,6 @@ package profiles import ( "bytes" "errors" - "io" "testing" "github.com/fleetdm/fleet/v4/server/fleet" @@ -19,18 +18,38 @@ func TestGetFleetdConfig(t *testing.T) { cmdOut *string cmdErr error wantOut *fleet.MDMAppleFleetdConfig - wantErr error + wantErr string }{ - {nil, testErr, nil, testErr}, - {ptr.String("invalid-xml"), nil, nil, io.EOF}, - {&emptyOutput, nil, nil, ErrNotFound}, - {&withFleetdConfig, nil, &fleet.MDMAppleFleetdConfig{EnrollSecret: "ENROLL_SECRET", FleetURL: "https://test.example.com"}, nil}, + {nil, testErr, nil, testErr.Error()}, + {ptr.String("invalid-json"), nil, nil, "unmarshaling configuration"}, + {ptr.String("{}"), nil, nil, ErrNotFound.Error()}, + { + ptr.String(`{"EnrollSecret": "ENROLL_SECRET", "FleetURL": "https://test.example.com"}`), + nil, + &fleet.MDMAppleFleetdConfig{ + EnrollSecret: "ENROLL_SECRET", + FleetURL: "https://test.example.com", + }, + "", + }, + { + ptr.String(`{"EnrollSecret": "ENROLL_SECRET", "FleetURL": ""}`), + nil, + nil, + ErrNotFound.Error(), + }, + { + ptr.String(`{"EnrollSecret": "", "FleetURL": "https://test.example.com"}`), + nil, + nil, + ErrNotFound.Error(), + }, } - origExecProfileCmd := execProfileCmd - t.Cleanup(func() { execProfileCmd = origExecProfileCmd }) + origExecScript := execScript + t.Cleanup(func() { execScript = origExecScript }) for _, c := range cases { - execProfileCmd = func() (*bytes.Buffer, error) { + execScript = func(script string) (*bytes.Buffer, error) { if c.cmdOut == nil { return nil, c.cmdErr } @@ -41,69 +60,12 @@ func TestGetFleetdConfig(t *testing.T) { } out, err := GetFleetdConfig() - require.ErrorIs(t, err, c.wantErr) + if c.wantErr != "" { + require.ErrorContains(t, err, c.wantErr) + } else { + require.NoError(t, err) + } 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 - - - -` -)