Re-implement enrollment and OTA profile customization (#214)

This commit is contained in:
Jesse Peterson
2017-07-12 17:09:36 -04:00
committed by GitHub
parent 52e0fe3ac1
commit 7d902cb588
4 changed files with 119 additions and 58 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/micromdm/micromdm/crypto"
"github.com/micromdm/micromdm/profile"
boltdepot "github.com/micromdm/scep/depot/bolt"
"github.com/fullsailor/pkcs7"
@@ -44,8 +45,8 @@ type otaEnrollmentRequest struct {
type mdmEnrollRequest struct{}
type mdmEnrollResponse struct {
Profile
type mobileconfigResponse struct {
profile.Mobileconfig
Err error `plist:"error,omitempty"`
}
@@ -71,12 +72,12 @@ func MakeGetEnrollEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
switch req := request.(type) {
case mdmEnrollRequest:
profile, err := s.Enroll(ctx)
return mdmEnrollResponse{profile, err}, nil
mc, err := s.Enroll(ctx)
return mobileconfigResponse{mc, err}, nil
case depEnrollmentRequest:
fmt.Printf("got DEP enrollment request from %s\n", req.Serial)
profile, err := s.Enroll(ctx)
return mdmEnrollResponse{profile, err}, nil
mc, err := s.Enroll(ctx)
return mobileconfigResponse{mc, err}, nil
default:
return nil, errors.New("unknown enrollment type")
}
@@ -85,8 +86,8 @@ func MakeGetEnrollEndpoint(s Service) endpoint.Endpoint {
func MakeOTAEnrollEndpoint(s Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
payload, err := s.OTAEnroll(ctx)
return mdmOTAEnrollResponse{payload, err}, nil
mc, err := s.OTAEnroll(ctx)
return mobileconfigResponse{mc, err}, nil
}
}
@@ -107,8 +108,8 @@ func MakeOTAPhase2Phase3Endpoint(s Service, scepDepot *boltdepot.Depot) endpoint
// signing certificate is signed by the Apple Device CA. this means
// we don't yet have a SCEP identity and thus are in Phase 2 of the
// OTA enrollment
profile, err := s.OTAPhase2(ctx)
return mdmEnrollResponse{profile, err}, nil
mc, err := s.OTAPhase2(ctx)
return mobileconfigResponse{mc, err}, nil
}
caChain, _, err := scepDepot.CA(nil)
@@ -133,10 +134,10 @@ func MakeOTAPhase2Phase3Endpoint(s Service, scepDepot *boltdepot.Depot) endpoint
// TODO: the SCEP CA checking ought to be more robust
// see: https://github.com/micromdm/scep/issues/32
profile, err := s.Enroll(ctx)
mc, err := s.Enroll(ctx)
// profile, err := s.OTAPhase3(ctx)
return mdmEnrollResponse{profile, err}, nil
return mobileconfigResponse{mc, err}, nil
}
return mdmEnrollResponse{Profile{}, errors.New("unauthorized client")}, nil
return mobileconfigResponse{profile.Mobileconfig{}, errors.New("unauthorized client")}, nil
}
}

View File

@@ -1,20 +1,30 @@
package enroll
import (
"bytes"
"crypto/x509"
"golang.org/x/net/context"
"io/ioutil"
"strings"
"github.com/groob/plist"
"golang.org/x/net/context"
"github.com/micromdm/micromdm/profile"
)
const (
EnrollmentProfileId string = "com.github.micromdm.micromdm.enroll"
OTAProfileId string = "com.github.micromdm.micromdm.ota"
)
type Service interface {
Enroll(ctx context.Context) (Profile, error)
OTAEnroll(ctx context.Context) (Payload, error)
OTAPhase2(ctx context.Context) (Profile, error)
OTAPhase3(ctx context.Context) (Profile, error)
Enroll(ctx context.Context) (profile.Mobileconfig, error)
OTAEnroll(ctx context.Context) (profile.Mobileconfig, error)
OTAPhase2(ctx context.Context) (profile.Mobileconfig, error)
OTAPhase3(ctx context.Context) (profile.Mobileconfig, error)
}
func NewService(pushTopic, caCertPath, scepURL, scepChallenge, url, tlsCertPath, scepSubject string) (Service, error) {
func NewService(pushTopic, caCertPath, scepURL, scepChallenge, url, tlsCertPath, scepSubject string, profileDB *profile.DB) (Service, error) {
var caCert, tlsCert []byte
var err error
@@ -57,6 +67,7 @@ func NewService(pushTopic, caCertPath, scepURL, scepChallenge, url, tlsCertPath,
Topic: pushTopic,
CACert: caCert,
TLSCert: tlsCert,
ProfileDB: profileDB,
}, nil
}
@@ -68,11 +79,51 @@ type service struct {
Topic string // APNS Topic for MDM notifications
CACert []byte
TLSCert []byte
ProfileDB *profile.DB
}
func (svc service) Enroll(ctx context.Context) (Profile, error) {
func profileOrPayloadFromFunc(f interface{}) (interface{}, error) {
fPayload, ok := f.(func() (Payload, error))
if !ok {
fProfile := f.(func() (Profile, error))
return fProfile()
}
return fPayload()
}
func profileOrPayloadToMobileconfig(in interface{}) (profile.Mobileconfig, error) {
if _, ok := in.(Payload); !ok {
_ = in.(Profile)
}
buf := new(bytes.Buffer)
enc := plist.NewEncoder(buf)
enc.Indent(" ")
err := enc.Encode(in)
return buf.Bytes(), err
}
func (svc service) findOrMakeMobileconfig(id string, f interface{}) (profile.Mobileconfig, error) {
p, err := svc.ProfileDB.ProfileById(id)
if err != nil {
if profile.IsNotFound(err) {
profile, err := profileOrPayloadFromFunc(f)
if err != nil {
return nil, err
}
return profileOrPayloadToMobileconfig(profile)
}
return nil, err
}
return p.Mobileconfig, nil
}
func (svc service) Enroll(ctx context.Context) (profile.Mobileconfig, error) {
return svc.findOrMakeMobileconfig(EnrollmentProfileId, svc.MakeEnrollmentProfile)
}
func (svc service) MakeEnrollmentProfile() (Profile, error) {
profile := NewProfile()
profile.PayloadIdentifier = "com.github.micromdm.micromdm.mdm"
profile.PayloadIdentifier = EnrollmentProfileId
profile.PayloadOrganization = "MicroMDM"
profile.PayloadDisplayName = "Enrollment Profile"
profile.PayloadDescription = "The server may alter your settings"
@@ -81,7 +132,7 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
mdmPayload := NewPayload("com.apple.mdm")
mdmPayload.PayloadDescription = "Enrolls with the MDM server"
mdmPayload.PayloadOrganization = "MicroMDM"
mdmPayload.PayloadIdentifier = "com.github.micromdm.mdm"
mdmPayload.PayloadIdentifier = EnrollmentProfileId + ".mdm"
mdmPayload.PayloadScope = "System"
mdmPayloadContent := MDMPayloadContent{
@@ -99,9 +150,9 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
if svc.SCEPURL != "" {
scepContent := SCEPPayloadContent{
URL: svc.SCEPURL,
Keysize: 1024,
Keysize: 2048,
KeyType: "RSA",
KeyUsage: 0,
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment),
Name: "Device Management Identity Certificate",
Subject: svc.SCEPSubject,
}
@@ -113,7 +164,7 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
scepPayload := NewPayload("com.apple.security.scep")
scepPayload.PayloadDescription = "Configures SCEP"
scepPayload.PayloadDisplayName = "SCEP"
scepPayload.PayloadIdentifier = "com.github.micromdm.scep"
scepPayload.PayloadIdentifier = EnrollmentProfileId + ".scep"
scepPayload.PayloadOrganization = "MicroMDM"
scepPayload.PayloadContent = scepContent
scepPayload.PayloadScope = "System"
@@ -128,7 +179,7 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
caPayload := NewPayload("com.apple.security.root")
caPayload.PayloadDisplayName = "Root certificate for MicroMDM"
caPayload.PayloadDescription = "Installs the root CA certificate for MicroMDM"
caPayload.PayloadIdentifier = "com.github.micromdm.ssl.ca"
caPayload.PayloadIdentifier = EnrollmentProfileId + ".cert.ca"
caPayload.PayloadContent = svc.CACert
payloadContent = append(payloadContent, *caPayload)
@@ -139,7 +190,7 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
tlsPayload := NewPayload("com.apple.security.pem")
tlsPayload.PayloadDisplayName = "Self-signed TLS certificate for MicroMDM"
tlsPayload.PayloadDescription = "Installs the TLS certificate for MicroMDM"
tlsPayload.PayloadIdentifier = "com.github.micromdm.tls"
tlsPayload.PayloadIdentifier = EnrollmentProfileId + ".cert.selfsigned"
tlsPayload.PayloadContent = svc.TLSCert
payloadContent = append(payloadContent, *tlsPayload)
@@ -151,9 +202,13 @@ func (svc service) Enroll(ctx context.Context) (Profile, error) {
}
// OTAEnroll returns an Over-the-Air "Profile Service" Payload for enrollment.
func (svc service) OTAEnroll(ctx context.Context) (Payload, error) {
func (svc service) OTAEnroll(ctx context.Context) (profile.Mobileconfig, error) {
return svc.findOrMakeMobileconfig(OTAProfileId, svc.MakeOTAEnrollPayload)
}
func (svc service) MakeOTAEnrollPayload() (Payload, error) {
payload := NewPayload("Profile Service")
payload.PayloadIdentifier = "com.github.micromdm.ota.profile-service"
payload.PayloadIdentifier = OTAProfileId
payload.PayloadDisplayName = "MicroMDM Profile Service"
payload.PayloadDescription = "Profile Service enrollment"
payload.PayloadOrganization = "MicroMDM"
@@ -168,20 +223,24 @@ func (svc service) OTAEnroll(ctx context.Context) (Payload, error) {
}
// OTAPhase2 returns a SCEP Profile for use in phase 2 of Over-the-Air enrollment.
func (svc service) OTAPhase2(ctx context.Context) (Profile, error) {
func (svc service) OTAPhase2(ctx context.Context) (profile.Mobileconfig, error) {
return svc.findOrMakeMobileconfig(OTAProfileId+".phase2", svc.MakeOTAPhase2Profile)
}
func (svc service) MakeOTAPhase2Profile() (Profile, error) {
profile := NewProfile()
profile.PayloadIdentifier = "com.github.micromdm.micromdm.ota-scep"
profile.PayloadIdentifier = OTAProfileId + ".phase2"
profile.PayloadOrganization = "MicroMDM"
profile.PayloadDisplayName = "Enrollment Profile"
profile.PayloadDisplayName = "OTA Phase 2"
profile.PayloadDescription = "The server may alter your settings"
profile.PayloadScope = "System"
scepContent := SCEPPayloadContent{
URL: svc.SCEPURL,
Keysize: 1024,
Keysize: 2048, // NOTE: OTA docs recommend 1024
KeyType: "RSA",
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment),
Name: "Device Management Identity Certificate",
Name: "OTA Phase 2 Certificate",
Subject: svc.SCEPSubject,
}
@@ -192,7 +251,7 @@ func (svc service) OTAPhase2(ctx context.Context) (Profile, error) {
scepPayload := NewPayload("com.apple.security.scep")
scepPayload.PayloadDescription = "Configures SCEP"
scepPayload.PayloadDisplayName = "SCEP"
scepPayload.PayloadIdentifier = "com.github.micromdm.scep"
scepPayload.PayloadIdentifier = OTAProfileId + ".phase2.scep"
scepPayload.PayloadOrganization = "MicroMDM"
scepPayload.PayloadContent = scepContent
scepPayload.PayloadScope = "System"
@@ -207,6 +266,6 @@ func (svc service) OTAPhase2(ctx context.Context) (Profile, error) {
// enrollment process. In our case this would probably be a device-specifc
// MDM enrollment payload.
// TODO: Not implemented.
func (svc service) OTAPhase3(ctx context.Context) (Profile, error) {
return Profile{}, nil
func (svc service) OTAPhase3(ctx context.Context) (profile.Mobileconfig, error) {
return profile.Mobileconfig{}, nil
}

View File

@@ -26,26 +26,26 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran
EnrollHandler: httptransport.NewServer(
endpoints.GetEnrollEndpoint,
decodeMDMEnrollRequest,
encodeResponse,
encodeMobileconfigResponse,
opts...,
),
OTAEnrollHandler: httptransport.NewServer(
endpoints.OTAEnrollEndpoint,
nilRequest,
encodeResponse,
decodeEmptyRequest,
encodeMobileconfigResponse,
opts...,
),
OTAPhase2Phase3Handler: httptransport.NewServer(
endpoints.OTAPhase2Phase3Endpoint,
decodeOTAPhase2Phase3Request,
encodeResponse,
encodeMobileconfigResponse,
opts...,
),
}
return h
}
func nilRequest(_ context.Context, _ *http.Request) (interface{}, error) {
func decodeEmptyRequest(_ context.Context, _ *http.Request) (interface{}, error) {
return nil, nil
}
@@ -85,16 +85,11 @@ func decodeMDMEnrollRequest(_ context.Context, r *http.Request) (interface{}, er
}
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
func encodeMobileconfigResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/x-apple-aspen-config")
enc := plist.NewEncoder(w)
enc.Indent(" ")
if err := enc.Encode(response); err != nil {
return err
}
return nil
mcResp := response.(mobileconfigResponse)
_, err := w.Write(mcResp.Mobileconfig)
return err
}
func decodeOTAPhase2Phase3Request(_ context.Context, r *http.Request) (interface{}, error) {

View File

@@ -142,7 +142,6 @@ func serve(args []string) error {
sm.setupBolt()
sm.loadPushCerts()
sm.setupSCEP(logger)
sm.setupEnrollmentService()
sm.setupCheckinService()
sm.setupPushService()
sm.setupCommandService()
@@ -157,12 +156,17 @@ func serve(args []string) error {
stdlog.Fatal(err)
}
profDB, err := profile.NewDB(sm.db)
sm.profileDB, err = profile.NewDB(sm.db)
if err != nil {
stdlog.Fatal(err)
}
bpDB, err := blueprint.NewDB(sm.db, profDB)
sm.setupEnrollmentService()
if sm.err != nil {
stdlog.Fatalf("enrollment service: %s", sm.err)
}
bpDB, err := blueprint.NewDB(sm.db, sm.profileDB)
if err != nil {
stdlog.Fatal(err)
}
@@ -212,7 +216,7 @@ func serve(args []string) error {
dc, err := sm.depClient()
if err != nil {
stdlog.Fatalf("creating DEP client %s\n", err)
stdlog.Fatalf("creating DEP client: %s\n", err)
}
tokenDB := &deptoken.DB{DB: sm.db, Publisher: sm.pubclient}
appDB := &appstore.Repo{Path: *flRepoPath}
@@ -223,7 +227,7 @@ func serve(args []string) error {
Devices: devDB,
Tokens: tokenDB,
Blueprints: bpDB,
Profiles: profDB,
Profiles: sm.profileDB,
Apps: appDB,
}
listsvc = l
@@ -254,7 +258,7 @@ func serve(args []string) error {
DEPClient: dc,
Blueprints: bpDB,
Tokens: tokenDB,
Profiles: profDB,
Profiles: sm.profileDB,
Apps: appDB,
}
applysvc = l
@@ -295,7 +299,7 @@ func serve(args []string) error {
listAPIHandlers := list.MakeHTTPHandlers(ctx, listEndpoints, connectOpts...)
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: profDB}
rmsvc := &remove.RemoveService{Blueprints: bpDB, Profiles: sm.profileDB}
removeAPIHandlers := remove.MakeHTTPHandlers(ctx, remove.MakeEndpoints(rmsvc), connectOpts...)
connectHandlers := connect.MakeHTTPHandlers(ctx, connectEndpoints, connectOpts...)
@@ -484,6 +488,7 @@ type config struct {
APNSPrivateKeyPass string
tlsCertPath string
scepDepot *boltdepot.Depot
profileDB *profile.DB
// TODO: refactor enroll service and remove the need to reference
// this on-disk cert. but it might be useful to keep the PEM
@@ -661,6 +666,7 @@ func (c *config) setupEnrollmentService() {
c.ServerPublicURL,
c.tlsCertPath,
SCEPCertificateSubject,
c.profileDB,
)
}