Switch to using jessepeterson/cfgprofiles for profile generation (#827)

* Switch to using jessepeterson/cfgprofiles for profile generation
This commit is contained in:
Jesse Peterson
2022-07-18 10:40:09 -07:00
committed by GitHub
parent b5609adc48
commit d556eb9880
5 changed files with 115 additions and 252 deletions

3
go.mod
View File

@@ -5,11 +5,12 @@ require (
github.com/boltdb/bolt v1.3.1
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17
github.com/go-kit/kit v0.7.0
github.com/google/uuid v1.1.1
github.com/google/uuid v1.1.2
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.6.2
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5
github.com/jessepeterson/cfgprofiles v0.2.0
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0
github.com/korylprince/go-macos-pkg v1.3.5

13
go.sum
View File

@@ -24,8 +24,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -36,20 +36,17 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0=
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
github.com/groob/plist v0.0.0-20180203051248-dd56909aee38 h1:afbUddvIjPRC7XHHgeSTRfzZtIxEsSl4VCxumLBGDJU=
github.com/groob/plist v0.0.0-20180203051248-dd56909aee38/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc=
github.com/groob/plist v0.0.0-20190114192801-a99fbe489d03/go.mod h1:qg2Nek0ND/hIr+nY8H1oVqEW2cLzVVNaAQ0QexOyjyc=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5 h1:saaSiB25B1wgaxrshQhurfPKUGJ4It3OxNJUy0rdOjU=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw=
github.com/jessepeterson/cfgprofiles v0.2.0 h1:yuAWfY5X0MZfWA1yGppnqIJr5zXziHSlutDKv5ST/wg=
github.com/jessepeterson/cfgprofiles v0.2.0/go.mod h1:bdPSaKnOBjiToJhSVaF2+0N3iSMvugICiC7kguXkD14=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0 h1:8UB0NJBoB/lN2O9zG9+U2klyYE0UhvN2Zrn7HBjhwNY=
github.com/kolide/kit v0.0.0-20180912215818-0c28f72eb2b0/go.mod h1:N3Yv8okDVC/5qZhPA9uxVYRfkp4mD2vrlQiSCWlNCpg=
github.com/korylprince/go-cpio-odc v0.9.3 h1:MGpo1PcJN2ajysEoAhF34zR/Peevo4N+T+OmhDLIUvk=
github.com/korylprince/go-cpio-odc v0.9.3/go.mod h1:1iHsjUXO64Hui0YsubGp0Tm/Uf04Iow2iy+1LJIarMw=
github.com/korylprince/go-cpio-odc v0.9.4 h1:N0Afrp7Z5qCZF8cbpzF5CvyiC/KHK5IcLJwqm5MWpNU=
github.com/korylprince/go-cpio-odc v0.9.4/go.mod h1:1iHsjUXO64Hui0YsubGp0Tm/Uf04Iow2iy+1LJIarMw=
github.com/korylprince/go-macos-pkg v1.3.4 h1:6McQ9bj3p/k/FeqFBHhdvXVvi+ftNcWXj4EWAEuEiz0=
github.com/korylprince/go-macos-pkg v1.3.4/go.mod h1:eLXotCZDK3vBt6AfDg62Fl20lCjrIm5e8V/8Q9YLHIo=
github.com/korylprince/go-macos-pkg v1.3.5 h1:jwwefLR1yRKQPNu51QTwk2B4abIjjISj/BHipcvKKGs=
github.com/korylprince/go-macos-pkg v1.3.5/go.mod h1:jlPGliXXjjCMpISR30/fL60hXKrCJ2LNQSptdx+WQkc=
github.com/korylprince/goxar v0.0.0-20211111233330-e9f257bcdf25 h1:XVf+U+WekiGPPD27w7qI+0Pe8NehaTWPqRuO3BddefY=

View File

@@ -1,149 +0,0 @@
package enroll
import (
"time"
"github.com/google/uuid"
)
type Payload struct {
PayloadType string `json:"type" db:"type"`
PayloadVersion int `json:"version" db:"version"`
PayloadIdentifier string `json:"identifier" db:"identifier"`
PayloadUUID string `json:"uuid" db:"uuid"`
PayloadDisplayName string `json:"displayname" db:"displayname"`
PayloadDescription string `json:"description,omitempty" db:"description"`
PayloadOrganization string `json:"organization,omitempty" db:"organization"`
PayloadScope string `json:"scope" db:"scope" plist:",omitempty"`
PayloadContent interface{} `json:"content,omitempty" plist:"PayloadContent,omitempty"`
}
type Profile struct {
PayloadContent []interface{} `json:"content,omitempty" db:"content"`
PayloadDescription string `json:"description,omitempty" db:"description"`
PayloadDisplayName string `json:"displayname,omitempty" db:"displayname"`
PayloadExpirationDate *time.Time `json:"expiration_date,omitempty" db:"expiration_date" plist:",omitempty"`
PayloadIdentifier string `json:"identifier" db:"identifier"`
PayloadOrganization string `json:"organization,omitempty" db:"organization"`
PayloadUUID string `json:"uuid" db:"uuid"`
PayloadRemovalDisallowed bool `json:"removal_disallowed" db:"removal_disallowed" plist:",omitempty"`
PayloadType string `json:"type" db:"type"`
PayloadVersion int `json:"version" db:"version"`
PayloadScope string `json:"scope" db:"scope" plist:",omitempty"`
RemovalDate *time.Time `json:"removal_date" db:"removal_date" plist:"-" plist:",omitempty"`
DurationUntilRemoval float32 `json:"duration_until_removal" db:"duration_until_removal" plist:",omitempty"`
ConsentText map[string]string `json:"consent_text" db:"consent_text" plist:",omitempty"`
}
func NewProfile() *Profile {
payloadUuid := uuid.New()
return &Profile{
PayloadVersion: 1,
PayloadType: "Configuration",
PayloadUUID: payloadUuid.String(),
}
}
func NewPayload(payloadType string) *Payload {
payloadUuid := uuid.New()
return &Payload{
PayloadVersion: 1,
PayloadType: payloadType,
PayloadUUID: payloadUuid.String(),
}
}
type SCEPPayloadContent struct {
CAFingerprint []byte `plist:"CAFingerprint,omitempty"` // NSData
Challenge string `plist:"Challenge,omitempty"`
Keysize int
KeyType string `plist:"Key Type"`
KeyUsage int `plist:"Key Usage"`
Name string
Subject [][][]string `plist:"Subject,omitempty"`
URL string
}
// AccessRights define the management rights of the MDM server over the device.
// May not be zero. If 2 is specified, 1 must also be specified. If 128 is specified, 64 must also be specified.
type AccessRights int
const (
// Allow inspection of installed configuration profiles.
ProfileInspection AccessRights = 1 << iota
// Allow installation and removal of configuration profiles.
ProfileInstallAndRemoval
// Allow device lock and passcode removal.
DeviceLock
// Allow device erase.
DeviceErase
// Allow query of Device Information (device capacity, serial number).
DeviceInformationQuery
// Allow query of Network Information (phone/SIM numbers, MAC addresses).
NetworkInformationQuery
// Allow inspection of installed provisioning profiles.
ProvisioningProfileInspection
// Allow installation and removal of provisioning profiles.
ProvisioningProfileInstallAndRemoval
// Allow inspection of installed applications.
ApplicationInspection
// Allow restriction-related queries.
RestrictionQuery
// Allow security-related queries.
SecurityQuery
// Allow manipulation of settings.
// Availability: Available in iOS 5.0 and later. Available in macOS 10.9 for certain commands.
SettingsManipulation
// Allow app management.
// Availability: Available in iOS 5.0 and later. Available in macOS 10.9 for certain commands.
AppManagement
)
func allRights() AccessRights {
return ProfileInspection |
ProfileInstallAndRemoval |
DeviceLock |
DeviceErase |
DeviceInformationQuery |
NetworkInformationQuery |
ProvisioningProfileInspection |
ProvisioningProfileInstallAndRemoval |
ApplicationInspection |
RestrictionQuery |
SecurityQuery |
SettingsManipulation |
AppManagement
}
// TODO: Actually this is one of those non-nested payloads that doesnt respect the PayloadContent key.
type MDMPayloadContent struct {
Payload
AccessRights AccessRights
CheckInURL string
CheckOutWhenRemoved bool
IdentityCertificateUUID string
ServerCapabilities []string `plist:"ServerCapabilities,omitempty"`
SignMessage bool `plist:"SignMessage,omitempty"`
ServerURL string
Topic string
}
type ProfileServicePayload struct {
URL string
Challenge string `plist:",omitempty"`
DeviceAttributes []string
}

View File

@@ -11,14 +11,13 @@ func TestEnrollProfile(t *testing.T) {
t.Fatal(err)
}
var payloadContent MDMPayloadContent
for _, payload := range profile.PayloadContent {
if c, ok := payload.(MDMPayloadContent); ok {
payloadContent = c
}
mdmPayloads := profile.MDMPayloads()
if len(mdmPayloads) != 1 {
t.Fatal("number of MDM payloads is not 1")
}
payloadContent := mdmPayloads[0]
if have, want := payloadContent.AccessRights, AccessRights(8191); have != want {
if have, want := payloadContent.AccessRights, 8191; have != want {
t.Errorf("have %d, want %d", have, want)
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/micromdm/scep/v2/challenge"
"github.com/groob/plist"
"github.com/jessepeterson/cfgprofiles"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
@@ -21,6 +22,17 @@ import (
const (
EnrollmentProfileId string = "com.github.micromdm.micromdm.enroll"
OTAProfileId string = "com.github.micromdm.micromdm.ota"
profilePayloadOrganization = "MicroMDM"
profilePayloadDisplayName = "Enrollment Profile"
profilePayloadDescription = "The server may alter your settings"
mdmPayloadDescription = "Enrolls with the MDM server"
mdmPayloadServerEndpoint = "/mdm/connect"
mdmPayloadCheckInEndpoint = "/mdm/checkin"
scepPayloadDescription = "Configures SCEP"
scepPayloadDisplayName = "SCEP"
)
type Service interface {
@@ -126,17 +138,20 @@ type TopicProvider interface {
}
func profileOrPayloadFromFunc(f interface{}) (interface{}, error) {
fPayload, ok := f.(func() (Payload, error))
fProfile, ok := f.(func() (*cfgprofiles.Profile, error))
if !ok {
fProfile := f.(func() (Profile, error))
return fProfile()
fPayload := f.(func() (*ProfileServicePayload, error))
return fPayload()
}
return fPayload()
return fProfile()
}
func profileOrPayloadToMobileconfig(in interface{}) (profile.Mobileconfig, error) {
if _, ok := in.(Payload); !ok {
_ = in.(Profile)
switch in.(type) {
case *ProfileServicePayload, *cfgprofiles.Profile:
break
default:
return nil, errors.New("invalid profile type")
}
buf := new(bytes.Buffer)
enc := plist.NewEncoder(buf)
@@ -164,88 +179,78 @@ func (svc *service) Enroll(ctx context.Context) (profile.Mobileconfig, error) {
return svc.findOrMakeMobileconfig(ctx, EnrollmentProfileId, svc.MakeEnrollmentProfile)
}
func (svc *service) scepChallenge() (challenge string, err error) {
if svc.SCEPChallengeStore != nil {
challenge, err = svc.SCEPChallengeStore.SCEPChallenge()
} else if svc.SCEPChallenge != "" {
challenge = svc.SCEPChallenge
}
return
}
const perUserConnections = "com.apple.mdm.per-user-connections"
const bootstrapToken = "com.apple.mdm.bootstraptoken"
func (svc *service) MakeEnrollmentProfile() (Profile, error) {
profile := NewProfile()
profile.PayloadIdentifier = EnrollmentProfileId
profile.PayloadOrganization = "MicroMDM"
profile.PayloadDisplayName = "Enrollment Profile"
profile.PayloadDescription = "The server may alter your settings"
func (svc *service) MakeEnrollmentProfile() (*cfgprofiles.Profile, error) {
profile := cfgprofiles.NewProfile(EnrollmentProfileId)
profile.PayloadScope = "System"
profile.PayloadOrganization = profilePayloadOrganization
profile.PayloadDisplayName = profilePayloadDisplayName
profile.PayloadDescription = profilePayloadDescription
mdmPayload := NewPayload("com.apple.mdm")
mdmPayload.PayloadDescription = "Enrolls with the MDM server"
mdmPayload.PayloadOrganization = "MicroMDM"
mdmPayload.PayloadIdentifier = EnrollmentProfileId + ".mdm"
mdmPayload.PayloadScope = "System"
mdmPayload := cfgprofiles.NewMDMPayload(EnrollmentProfileId + ".mdm")
mdmPayload.PayloadOrganization = profilePayloadOrganization
mdmPayload.PayloadDescription = mdmPayloadDescription
mdmPayload.ServerURL = svc.URL + mdmPayloadServerEndpoint
mdmPayload.CheckInURL = svc.URL + mdmPayloadCheckInEndpoint
mdmPayload.CheckOutWhenRemoved = true
mdmPayload.AccessRights = 8191
svc.mu.Lock()
topic := svc.Topic
mdmPayload.Topic = svc.Topic
svc.mu.Unlock()
mdmPayloadContent := MDMPayloadContent{
Payload: *mdmPayload,
AccessRights: allRights(),
CheckInURL: svc.URL + "/mdm/checkin",
CheckOutWhenRemoved: true,
ServerURL: svc.URL + "/mdm/connect",
Topic: topic,
SignMessage: true,
ServerCapabilities: []string{perUserConnections, bootstrapToken},
}
payloadContent := []interface{}{}
mdmPayload.SignMessage = true
mdmPayload.ServerCapabilities = []string{perUserConnections, bootstrapToken}
if svc.SCEPURL != "" {
scepContent := SCEPPayloadContent{
scepPayload := cfgprofiles.NewSCEPPayload(EnrollmentProfileId + ".scep")
scepPayload.PayloadDescription = scepPayloadDescription
scepPayload.PayloadDisplayName = scepPayloadDisplayName
scepPayload.PayloadOrganization = profilePayloadOrganization
scepPayload.PayloadContent = cfgprofiles.SCEPPayloadContent{
URL: svc.SCEPURL,
Keysize: 2048,
KeySize: 2048,
KeyType: "RSA",
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment),
Name: "Device Management Identity Certificate",
Subject: svc.SCEPSubject,
}
if svc.SCEPChallengeStore != nil {
challenge, err := svc.SCEPChallengeStore.SCEPChallenge()
if err != nil {
return *profile, err
}
scepContent.Challenge = challenge
} else if svc.SCEPChallenge != "" {
scepContent.Challenge = svc.SCEPChallenge
var err error
scepPayload.PayloadContent.Challenge, err = svc.scepChallenge()
if err != nil {
return nil, err
}
scepPayload := NewPayload("com.apple.security.scep")
scepPayload.PayloadDescription = "Configures SCEP"
scepPayload.PayloadDisplayName = "SCEP"
scepPayload.PayloadIdentifier = EnrollmentProfileId + ".scep"
scepPayload.PayloadOrganization = "MicroMDM"
scepPayload.PayloadContent = scepContent
scepPayload.PayloadScope = "System"
payloadContent = append(payloadContent, *scepPayload)
mdmPayloadContent.IdentityCertificateUUID = scepPayload.PayloadUUID
profile.AddPayload(scepPayload)
mdmPayload.IdentityCertificateUUID = scepPayload.PayloadUUID
}
payloadContent = append(payloadContent, mdmPayloadContent)
profile.AddPayload(mdmPayload)
// Client needs to trust us at this point if we are using a self signed certificate.
if len(svc.TLSCert) > 0 {
tlsPayload := NewPayload("com.apple.security.pem")
tlsPayload := cfgprofiles.NewCertificatePKCS1Payload(EnrollmentProfileId + ".cert.selfsigned")
tlsPayload.PayloadDisplayName = "Self-signed TLS certificate for MicroMDM"
tlsPayload.PayloadDescription = "Installs the TLS certificate for MicroMDM"
tlsPayload.PayloadIdentifier = EnrollmentProfileId + ".cert.selfsigned"
tlsPayload.PayloadContent = svc.TLSCert
payloadContent = append(payloadContent, *tlsPayload)
profile.AddPayload(tlsPayload)
}
profile.PayloadContent = payloadContent
return *profile, nil
return profile, nil
}
// OTAEnroll returns an Over-the-Air "Profile Service" Payload for enrollment.
@@ -253,20 +258,32 @@ func (svc *service) OTAEnroll(ctx context.Context) (profile.Mobileconfig, error)
return svc.findOrMakeMobileconfig(ctx, OTAProfileId, svc.MakeOTAEnrollPayload)
}
func (svc *service) MakeOTAEnrollPayload() (Payload, error) {
payload := NewPayload("Profile Service")
payload.PayloadIdentifier = OTAProfileId
payload.PayloadDisplayName = "MicroMDM Profile Service"
payload.PayloadDescription = "Profile Service enrollment"
payload.PayloadOrganization = "MicroMDM"
payload.PayloadContent = ProfileServicePayload{
URL: svc.URL + "/ota/phase23",
Challenge: "",
DeviceAttributes: []string{"UDID", "VERSION", "PRODUCT", "SERIAL", "MEID", "IMEI"},
type ProfileServicePayloadContent struct {
URL string
Challenge string `plist:",omitempty"`
DeviceAttributes []string
}
type ProfileServicePayload struct {
*cfgprofiles.Payload
PayloadContent ProfileServicePayloadContent
}
func (svc *service) MakeOTAEnrollPayload() (*ProfileServicePayload, error) {
payload := &ProfileServicePayload{
Payload: cfgprofiles.NewPayload("Profile Service", OTAProfileId),
PayloadContent: ProfileServicePayloadContent{
URL: svc.URL + "/ota/phase23",
Challenge: "",
DeviceAttributes: []string{"UDID", "VERSION", "PRODUCT", "SERIAL", "MEID", "IMEI"},
},
}
payload.PayloadOrganization = profilePayloadOrganization
payload.PayloadDescription = "Profile Service enrollment"
payload.PayloadDisplayName = "MicroMDM Profile Service"
// yes, this is a bare Payload, not a Profile
return *payload, nil
return payload, nil
}
// OTAPhase2 returns a SCEP Profile for use in phase 2 of Over-the-Air enrollment.
@@ -274,38 +291,36 @@ func (svc *service) OTAPhase2(ctx context.Context) (profile.Mobileconfig, error)
return svc.findOrMakeMobileconfig(ctx, OTAProfileId+".phase2", svc.MakeOTAPhase2Profile)
}
func (svc *service) MakeOTAPhase2Profile() (Profile, error) {
profile := NewProfile()
profile.PayloadIdentifier = OTAProfileId + ".phase2"
profile.PayloadOrganization = "MicroMDM"
func (svc *service) MakeOTAPhase2Profile() (*cfgprofiles.Profile, error) {
profile := cfgprofiles.NewProfile(OTAProfileId + ".phase2")
profile.PayloadOrganization = profilePayloadOrganization
profile.PayloadDisplayName = "OTA Phase 2"
profile.PayloadDescription = "The server may alter your settings"
profile.PayloadDescription = profilePayloadDescription
profile.PayloadScope = "System"
scepContent := SCEPPayloadContent{
scepPayload := cfgprofiles.NewSCEPPayload(OTAProfileId + ".phase2.scep")
scepPayload.PayloadDescription = scepPayloadDescription
scepPayload.PayloadDisplayName = scepPayloadDisplayName
scepPayload.PayloadOrganization = profilePayloadOrganization
scepPayload.PayloadContent = cfgprofiles.SCEPPayloadContent{
URL: svc.SCEPURL,
Keysize: 2048, // NOTE: OTA docs recommend 1024
KeySize: 2048, // NOTE: OTA docs recommend 1024
KeyType: "RSA",
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment),
Name: "OTA Phase 2 Certificate",
Subject: svc.SCEPSubject,
}
if svc.SCEPChallenge != "" {
scepContent.Challenge = svc.SCEPChallenge
var err error
scepPayload.PayloadContent.Challenge, err = svc.scepChallenge()
if err != nil {
return profile, err
}
scepPayload := NewPayload("com.apple.security.scep")
scepPayload.PayloadDescription = "Configures SCEP"
scepPayload.PayloadDisplayName = "SCEP"
scepPayload.PayloadIdentifier = OTAProfileId + ".phase2.scep"
scepPayload.PayloadOrganization = "MicroMDM"
scepPayload.PayloadContent = scepContent
scepPayload.PayloadScope = "System"
profile.AddPayload(scepPayload)
profile.PayloadContent = append(profile.PayloadContent, *scepPayload)
return *profile, nil
return profile, nil
}
// OTAPhase3 returns a Profile for use in phase 3 of Over-the-Air profile enrollment.