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
This commit is contained in:
Roberto Dip 2023-09-07 15:13:32 -03:00 committed by GitHub
parent 811e38c0f2
commit e023f84914
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 73 deletions

View file

@ -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 {

View file

@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>`
withFleetdConfig = `
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_computerlevel</key>
<array>
<dict>
<key>ProfileDescription</key>
<string>test descripiton</string>
<key>ProfileDisplayName</key>
<string>test name</string>
<key>ProfileIdentifier</key>
<string>com.fleetdm.fleetd.config</string>
<key>ProfileInstallDate</key>
<string>2023-02-27 18:55:07 +0000</string>
<key>ProfileItems</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>EnrollSecret</key>
<string>ENROLL_SECRET</string>
<key>FleetURL</key>
<string>https://test.example.com</string>
</dict>
<key>PayloadDescription</key>
<string>test description</string>
<key>PayloadDisplayName</key>
<string>test name</string>
<key>PayloadIdentifier</key>
<string>com.fleetdm.fleetd.config</string>
<key>PayloadType</key>
<string>com.fleetdm.fleetd</string>
<key>PayloadUUID</key>
<string>0C6AFB45-01B6-4E19-944A-123CD16381C7</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>ProfileRemovalDisallowed</key>
<string>true</string>
<key>ProfileType</key>
<string>Configuration</string>
<key>ProfileUUID</key>
<string>8D0F62E6-E24F-4B2F-AFA8-CAC1F07F4FDC</string>
<key>ProfileVersion</key>
<integer>1</integer>
</dict>
</array>
</dict>
</plist>`
)
func TestIsEnrolledInMDM(t *testing.T) {
cases := []struct {
cmdOut *string

View file

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