mirror of
https://github.com/fleetdm/fleet
synced 2026-04-30 18:07:56 +00:00
568 lines
16 KiB
Go
568 lines
16 KiB
Go
//go:build darwin
|
|
|
|
package profiles
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestGetFleetdConfig(t *testing.T) {
|
|
testErr := errors.New("test error")
|
|
cases := []struct {
|
|
cmdOut *string
|
|
cmdErr error
|
|
wantOut *fleet.MDMAppleFleetdConfig
|
|
wantErr error
|
|
}{
|
|
{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},
|
|
}
|
|
|
|
origExecProfileCmd := execProfileCmd
|
|
t.Cleanup(func() { execProfileCmd = origExecProfileCmd })
|
|
for _, c := range cases {
|
|
execProfileCmd = func() (*bytes.Buffer, error) {
|
|
if c.cmdOut == nil {
|
|
return nil, c.cmdErr
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.WriteString(*c.cmdOut)
|
|
return &buf, nil
|
|
}
|
|
|
|
out, err := GetFleetdConfig()
|
|
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>`
|
|
|
|
withFleetdConfigAndEnrollment = `
|
|
<?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>
|
|
<dict>
|
|
<key>ProfileDisplayName</key>
|
|
<string>f1337 enrollment</string>
|
|
<key>ProfileIdentifier</key>
|
|
<string>com.fleetdm.fleet.mdm.apple</string>
|
|
<key>ProfileInstallDate</key>
|
|
<string>2023-02-27 18:55:07 +0000</string>
|
|
<key>ProfileItems</key>
|
|
<array>
|
|
<dict>
|
|
<key>PayloadContent</key>
|
|
<dict/>
|
|
<key>PayloadIdentifier</key>
|
|
<string>com.fleetdm.fleet.mdm.apple.scep</string>
|
|
<key>PayloadType</key>
|
|
<string>com.apple.security.scep</string>
|
|
<key>PayloadUUID</key>
|
|
<string>BCA53F9D-5DD2-494D-98D3-0D0F20FF6BA1</string>
|
|
<key>PayloadVersion</key>
|
|
<integer>1</integer>
|
|
</dict>
|
|
<dict>
|
|
<key>PayloadContent</key>
|
|
<dict>
|
|
<key>AccessRights</key>
|
|
<integer>8191</integer>
|
|
<key>CheckOutWhenRemoved</key>
|
|
<true/>
|
|
<key>EndUserEmail</key>
|
|
<string>user@example.com</string>
|
|
<key>ServerCapabilities</key>
|
|
<array>
|
|
<string>com.apple.mdm.per-user-connections</string>
|
|
<string>com.apple.mdm.bootstraptoken</string>
|
|
</array>
|
|
<key>ServerURL</key>
|
|
<string>https://test.example.com</string>
|
|
</dict>
|
|
<key>PayloadIdentifier</key>
|
|
<string>com.fleetdm.fleet.mdm.apple.mdm</string>
|
|
<key>PayloadType</key>
|
|
<string>com.apple.mdm</string>
|
|
<key>PayloadUUID</key>
|
|
<string>29713130-1602-4D27-90C9-B822A295E44E</string>
|
|
<key>PayloadVersion</key>
|
|
<integer>1</integer>
|
|
</dict>
|
|
</array>
|
|
<key>ProfileOrganization</key>
|
|
<string>f1337</string>
|
|
<key>ProfileType</key>
|
|
<string>Configuration</string>
|
|
<key>ProfileUUID</key>
|
|
<string>5ACABE91-CE30-4C05-93E3-B235C152404E</string>
|
|
<key>ProfileVersion</key>
|
|
<integer>1</integer>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
</plist>`
|
|
)
|
|
|
|
func TestCustomInstallerWorkflow(t *testing.T) {
|
|
origExecProfileCmd := execProfileCmd
|
|
t.Cleanup(func() { execProfileCmd = origExecProfileCmd })
|
|
|
|
for _, c := range []struct {
|
|
name string
|
|
mockOut string
|
|
wantEmail string
|
|
wantErr error
|
|
}{
|
|
{"happy path", withFleetdConfigAndEnrollment, "user@example.com", nil},
|
|
{"empty profiles", emptyOutput, "", ErrNotFound},
|
|
{"no enrollment payload", withFleetdConfig, "", ErrNotFound},
|
|
{"wrong payload identifier", strings.Replace(withFleetdConfigAndEnrollment, mobileconfig.FleetEnrollmentPayloadIdentifier, "wrong-identifier", 1), "", ErrNotFound},
|
|
{"no end user email key", strings.Replace(withFleetdConfigAndEnrollment, "EndUserEmail", "WrongKey", 1), "", ErrNotFound},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
execProfileCmd = func() (*bytes.Buffer, error) {
|
|
var buf bytes.Buffer
|
|
buf.WriteString(c.mockOut)
|
|
return &buf, nil
|
|
}
|
|
|
|
gotContent, err := GetCustomEnrollmentProfileEndUserEmail()
|
|
if c.wantErr != nil {
|
|
require.ErrorIs(t, err, c.wantErr)
|
|
require.Empty(t, gotContent)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, "user@example.com", gotContent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsEnrolledInMDM(t *testing.T) {
|
|
cases := []struct {
|
|
cmdOut *string
|
|
cmdErr error
|
|
wantEnrolled bool
|
|
wantURL string
|
|
wantErr bool
|
|
}{
|
|
{nil, errors.New("test error"), false, "", true},
|
|
{ptr.String(""), nil, false, "", false},
|
|
{ptr.String(`
|
|
Enrolled via DEP: No
|
|
MDM enrollment: No
|
|
`), nil, false, "", false},
|
|
{
|
|
ptr.String(`
|
|
Enrolled via DEP: Yes
|
|
MDM enrollment: Yes
|
|
MDM server: https://test.example.com
|
|
`),
|
|
nil,
|
|
true,
|
|
"https://test.example.com",
|
|
false,
|
|
},
|
|
{
|
|
ptr.String(`
|
|
Enrolled via DEP: Yes
|
|
MDM enrollment: Yes
|
|
MDM server / https://test.example.com
|
|
`),
|
|
nil,
|
|
true,
|
|
"//test.example.com",
|
|
false,
|
|
},
|
|
{
|
|
ptr.String(`
|
|
Enrolled via DEP: Yes
|
|
MDM enrollment: Yes
|
|
MDM server: https://valid.com/mdm/apple/mdm
|
|
`),
|
|
nil,
|
|
true,
|
|
"https://valid.com/mdm/apple/mdm",
|
|
false,
|
|
},
|
|
}
|
|
|
|
origCmd := getMDMInfoFromProfilesCmd
|
|
t.Cleanup(func() { getMDMInfoFromProfilesCmd = origCmd })
|
|
for _, c := range cases {
|
|
getMDMInfoFromProfilesCmd = func() ([]byte, error) {
|
|
if c.cmdOut == nil {
|
|
return nil, c.cmdErr
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
buf.WriteString(*c.cmdOut)
|
|
return []byte(*c.cmdOut), nil
|
|
}
|
|
|
|
enrolled, url, err := IsEnrolledInMDM()
|
|
if c.wantErr {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, c.wantEnrolled, enrolled)
|
|
require.Equal(t, c.wantURL, url)
|
|
}
|
|
}
|
|
|
|
func TestCheckAssignedEnrollmentProfile(t *testing.T) {
|
|
fleetURL := "https://valid.com"
|
|
cases := []struct {
|
|
name string
|
|
cmdOut *string
|
|
cmdErr error
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "command error",
|
|
cmdOut: nil,
|
|
cmdErr: errors.New("some command error"),
|
|
wantErr: errors.New("some command error"),
|
|
},
|
|
{
|
|
name: "empty output",
|
|
cmdOut: ptr.String(""),
|
|
cmdErr: nil,
|
|
wantErr: errors.New("parsing profiles output: expected at least 2 lines but got 1"),
|
|
},
|
|
{
|
|
name: "null profile",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
(null)
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: errors.New("parsing profiles output: received null device enrollment configuration"),
|
|
},
|
|
{
|
|
name: "mismatch profile",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "https://test.example.com/mdm/apple/enroll?token=1234";
|
|
ConfigurationWebURL = "https://test.example.com/mdm/apple/enroll?token=1234";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: errors.New(`server url: expected 'valid.com' but found 'test.example.com'`),
|
|
},
|
|
{
|
|
name: "match profile",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "https://test.example.com/mdm/apple/enroll?token=1234";
|
|
ConfigurationWebURL = "https://valid.com?token=1234";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "mixed case match configuration web URL",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "https://test.ExaMplE.com/mdm/apple/enroll?token=1234";
|
|
ConfigurationWebURL = "https://vaLiD.com?tOken=1234";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "mixed case match configuration URL but wrong configuration web URL",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "https://vaLiD.com?tOken=1234";
|
|
ConfigurationWebURL = "https://test.ExaMplE.com/mdm/apple/enroll?token=1234";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: errors.New(`server url: expected 'valid.com' but found 'test.ExaMplE.com'`),
|
|
},
|
|
{
|
|
name: "match configuration URL and empty configuration web URL",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "https://valid.com?token=1234";
|
|
ConfigurationWebURL = "";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "mixed case match configuration web URL and empty configuration URL",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "";
|
|
ConfigurationWebURL = "https://vaLiD.com?tOken=1234";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: nil,
|
|
},
|
|
|
|
{
|
|
name: "unparseable URL",
|
|
cmdOut: ptr.String(`Device Enrollment configuration:
|
|
{
|
|
AllowPairing = 1;
|
|
AutoAdvanceSetup = 0;
|
|
AwaitDeviceConfigured = 0;
|
|
ConfigurationURL = "://invalid-url";
|
|
ConfigurationWebURL = "";
|
|
...
|
|
}
|
|
`),
|
|
cmdErr: nil,
|
|
wantErr: errors.New("parsing profiles output: unable to parse server url"),
|
|
},
|
|
}
|
|
|
|
origCmd := showEnrollmentProfileCmd
|
|
t.Cleanup(func() { showEnrollmentProfileCmd = origCmd })
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
showEnrollmentProfileCmd = func() ([]byte, error) {
|
|
if c.cmdOut == nil {
|
|
return nil, c.cmdErr
|
|
}
|
|
var buf bytes.Buffer
|
|
buf.WriteString(*c.cmdOut)
|
|
return []byte(*c.cmdOut), nil
|
|
}
|
|
|
|
err := CheckAssignedEnrollmentProfile(fleetURL)
|
|
if c.wantErr != nil {
|
|
require.ErrorContains(t, err, c.wantErr.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetProfilePayloadContent(t *testing.T) {
|
|
origExecProfileCmd := execProfileCmd
|
|
t.Cleanup(func() { execProfileCmd = origExecProfileCmd })
|
|
|
|
execProfileCmd = func() (*bytes.Buffer, error) {
|
|
var buf bytes.Buffer
|
|
buf.WriteString(withFleetdConfigAndEnrollment)
|
|
return &buf, nil
|
|
}
|
|
|
|
// mismatched int type is not acceptable
|
|
_, err := getProfilePayloadContent[int]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.ErrorContains(t, err, "plist: cannot unmarshal dict into Go value of type int")
|
|
|
|
// mismatched string type is not acceptable
|
|
_, err = getProfilePayloadContent[string]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.ErrorContains(t, err, "plist: cannot unmarshal dict into Go value of type string")
|
|
|
|
// mismatched bool type is not acceptable
|
|
_, err = getProfilePayloadContent[bool]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.ErrorContains(t, err, "plist: cannot unmarshal dict into Go value of type bool")
|
|
|
|
// mismatched slice type is not acceptable
|
|
_, err = getProfilePayloadContent[[]string]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.ErrorContains(t, err, "plist: cannot unmarshal dict into Go value of type []string")
|
|
|
|
// mismatched []byte type is not acceptable
|
|
_, err = getProfilePayloadContent[[]byte]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.ErrorContains(t, err, "plist: cannot unmarshal dict into Go value of type []uint8")
|
|
|
|
// mismatched struct type is acceptable, but result is empty
|
|
type wrongStruct struct {
|
|
foo string
|
|
bar int
|
|
}
|
|
ws, err := getProfilePayloadContent[wrongStruct]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ws)
|
|
require.Empty(t, ws.bar)
|
|
require.Empty(t, ws.foo)
|
|
|
|
// struct type is acceptable and returns the expected value type corresponds to the payload identifier
|
|
c, err := getProfilePayloadContent[fleet.MDMAppleFleetdConfig]("com.fleetdm.fleetd.config")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, c)
|
|
require.Equal(t, *c, fleet.MDMAppleFleetdConfig{EnrollSecret: "ENROLL_SECRET", FleetURL: "https://test.example.com"})
|
|
|
|
// struct type is acceptable and returns the expected value type corresponds to the payload identifier
|
|
e, err := getProfilePayloadContent[fleet.MDMCustomEnrollmentProfileItem]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, e)
|
|
require.Equal(t, *e, fleet.MDMCustomEnrollmentProfileItem{EndUserEmail: "user@example.com"})
|
|
|
|
// map type is acceptable
|
|
m, err := getProfilePayloadContent[map[string]any]("com.fleetdm.fleet.mdm.apple.mdm")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, m)
|
|
gotMap := *m
|
|
v, ok := gotMap["EndUserEmail"]
|
|
require.True(t, ok)
|
|
require.Equal(t, "user@example.com", v)
|
|
_, ok = gotMap["EnrollSecret"]
|
|
require.False(t, ok)
|
|
_, ok = gotMap["FleetURL"]
|
|
require.False(t, ok)
|
|
|
|
// map type is acceptable
|
|
m2, err := getProfilePayloadContent[map[string]any]("com.fleetdm.fleetd.config")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, m)
|
|
gotMap = *m2
|
|
v, ok = gotMap["EnrollSecret"]
|
|
require.True(t, ok)
|
|
require.Equal(t, "ENROLL_SECRET", v)
|
|
v, ok = gotMap["FleetURL"]
|
|
require.True(t, ok)
|
|
require.Equal(t, "https://test.example.com", v)
|
|
_, ok = gotMap["EndUserEmail"]
|
|
require.False(t, ok)
|
|
}
|