-
Notifications
You must be signed in to change notification settings - Fork 102
/
mdm.go
158 lines (141 loc) · 4.75 KB
/
mdm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package table
import (
"bytes"
"context"
"os/exec"
"strconv"
"time"
"github.com/go-kit/kit/log"
"github.com/groob/plist"
"github.com/kolide/osquery-go/plugin/table"
"github.com/pkg/errors"
)
func MDMInfo(logger log.Logger) *table.Plugin {
columns := []table.ColumnDefinition{
table.TextColumn("enrolled"),
table.TextColumn("server_url"),
table.TextColumn("checkin_url"),
table.IntegerColumn("access_rights"),
table.TextColumn("install_date"),
table.TextColumn("payload_identifier"),
table.TextColumn("topic"),
table.TextColumn("sign_message"),
table.TextColumn("identity_certificate_uuid"),
table.TextColumn("has_scep_payload"),
table.TextColumn("installed_from_dep"),
table.TextColumn("user_approved"),
}
return table.NewPlugin("kolide_mdm_info", columns, generateMDMInfo)
}
func generateMDMInfo(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
profiles, err := getMDMProfile(ctx)
if err != nil {
return nil, err
}
depEnrolled, userApproved := "unknown", "unknown"
status, err := getMDMProfileStatus(ctx)
if err == nil { // only supported on 10.13.4+
depEnrolled = strconv.FormatBool(status.DEPEnrolled)
userApproved = strconv.FormatBool(status.UserApproved)
}
var enrollProfileItems []profileItem
var results []map[string]string
var mdmResults map[string]string
for _, payload := range profiles.ComputerLevel {
for _, item := range payload.ProfileItems {
if item.PayloadContent == nil {
continue
}
if item.PayloadType == "com.apple.mdm" {
enrollProfile := item.PayloadContent
enrollProfileItems = payload.ProfileItems
mdmResults = map[string]string{
"enrolled": "true",
"server_url": enrollProfile.ServerURL,
"checkin_url": enrollProfile.CheckInURL,
"access_rights": strconv.Itoa(enrollProfile.AccessRights),
"install_date": payload.ProfileInstallDate,
"payload_identifier": payload.ProfileIdentifier,
"sign_message": strconv.FormatBool(enrollProfile.SignMessage),
"topic": enrollProfile.Topic,
"identity_certificate_uuid": enrollProfile.IdentityCertificateUUID,
"installed_from_dep": depEnrolled,
"user_approved": userApproved,
}
break
}
}
}
if len(enrollProfileItems) != 0 {
for _, item := range enrollProfileItems {
if item.PayloadType == "com.apple.security.scep" {
mdmResults["has_scep_payload"] = "true"
}
}
results = append(results, mdmResults)
} else {
results = []map[string]string{map[string]string{"enrolled": "false"}}
}
return results, nil
}
func getMDMProfile(ctx context.Context) (*profilesOutput, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/bin/profiles", "-L", "-o", "stdout-xml")
out, err := cmd.Output()
if err != nil {
return nil, errors.Wrap(err, "calling /usr/bin/profiles to get MDM profile payload")
}
var profiles profilesOutput
if err := plist.Unmarshal(out, &profiles); err != nil {
return nil, errors.Wrap(err, "unmarshal profiles output")
}
return &profiles, nil
}
type profilesOutput struct {
ComputerLevel []profilePayload `plist:"_computerlevel"`
}
type profilePayload struct {
ProfileIdentifier string
ProfileInstallDate string
ProfileItems []profileItem
}
type profileItem struct {
PayloadContent *payloadContent
PayloadType string
}
type payloadContent struct {
AccessRights int
CheckInURL string
ServerURL string
ServerCapabilities []string
Topic string
IdentityCertificateUUID string
SignMessage bool
}
func getMDMProfileStatus(ctx context.Context) (profileStatus, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/bin/profiles", "status", "-type", "enrollment")
out, err := cmd.Output()
if err != nil {
return profileStatus{}, errors.Wrap(err, "calling /usr/bin/profiles to get MDM profile status")
}
lines := bytes.Split(out, []byte("\n"))
depEnrollmentParts := bytes.SplitN(lines[0], []byte(":"), 2)
if len(depEnrollmentParts) < 2 {
return profileStatus{}, errors.Errorf("mdm: could not split the DEP Enrollment source %s", string(out))
}
enrollmentStatusParts := bytes.SplitN(lines[1], []byte(":"), 2)
if len(enrollmentStatusParts) < 2 {
return profileStatus{}, errors.Errorf("mdm: could not split the DEP Enrollment status %s", string(out))
}
return profileStatus{
DEPEnrolled: bytes.Contains(depEnrollmentParts[1], []byte("Yes")),
UserApproved: bytes.Contains(enrollmentStatusParts[1], []byte("Approved")),
}, nil
}
type profileStatus struct {
DEPEnrolled bool
UserApproved bool
}