mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-11 20:45:32 +08:00
Re-implement enrollment and OTA profile customization (#214)
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
20
serve.go
20
serve.go
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user