mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
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:
parent
811e38c0f2
commit
e023f84914
3 changed files with 119 additions and 73 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue