add profile enrollment

This commit is contained in:
Victor Vrantchan
2016-04-24 01:38:40 -04:00
parent b79fd33fe0
commit 3f112b8ce6
6 changed files with 102 additions and 2 deletions

View File

@@ -1,9 +1,11 @@
package checkin
import (
"io/ioutil"
"log"
"net/http"
"github.com/fullsailor/pkcs7"
"github.com/groob/plist"
)
@@ -15,6 +17,26 @@ func decodeMDMCheckinRequest(r *http.Request) (interface{}, error) {
return request, nil
}
// The enrollment request is PkCS7 signed.
// We'll ignore everything but the content for now
func decodeMDMEnrollmentRequest(r *http.Request) (interface{}, error) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
p7, err := pkcs7.Parse(data)
if err != nil {
return nil, err
}
// TODO: We should verify but not currently possible. Apple
// does no provide a cert for the CA.
var request depEnrollmentRequest
if err := plist.Unmarshal(p7.Content, &request); err != nil {
return nil, err
}
return request, nil
}
// errorer is implemented by all concrete response types. It allows us to
// change the HTTP response code without needing to trigger an endpoint
// (transport-level) error. For more information, read the big comment in
@@ -39,6 +61,18 @@ func encodeResponse(w http.ResponseWriter, response interface{}) error {
return enc.Encode(response)
}
func enrollResponse(w http.ResponseWriter, response interface{}) error {
if e, ok := response.(errorer); ok && e.error() != nil {
// Not a Go kit transport error, but a business-logic error.
// Provide those as HTTP errors.
encodeError(w, e.error())
return nil
}
resp := response.(depEnrollmentResponse)
w.Write(resp.Profile)
return nil
}
func encodeError(w http.ResponseWriter, err error) {
w.WriteHeader(codeFrom(err))
response := map[string]interface{}{

View File

@@ -25,3 +25,14 @@ func makeCheckinEndpoint(svc MDMCheckinService) endpoint.Endpoint {
return mdmCheckinResponse{}, nil
}
}
func makeEnrollmentEndpoint(svc MDMCheckinService) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(depEnrollmentRequest)
profile, err := svc.Enroll(req.DEPEnrollmentRequest.UDID)
if err != nil {
return depEnrollmentResponse{Err: err}, nil
}
return depEnrollmentResponse{Profile: []byte(*profile)}, nil
}
}

View File

@@ -11,3 +11,14 @@ type mdmCheckinResponse struct {
}
func (r mdmCheckinResponse) error() error { return r.Err }
type depEnrollmentRequest struct {
mdm.DEPEnrollmentRequest
}
type depEnrollmentResponse struct {
Profile []byte // MDM Enrollment Profile
Err error `plist:"error,omitempty"`
}
func (r depEnrollmentResponse) error() error { return r.Err }

View File

@@ -12,6 +12,7 @@ import (
"github.com/go-kit/kit/metrics"
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
httptransport "github.com/go-kit/kit/transport/http"
"github.com/gorilla/mux"
"github.com/micromdm/mdm"
"github.com/micromdm/micromdm/device"
stdprometheus "github.com/prometheus/client_golang/prometheus"
@@ -25,6 +26,7 @@ type MDMCheckinService interface {
Authenticate(mdm.CheckinCommand) error
TokenUpdate(mdm.CheckinCommand) error
Checkout(mdm.CheckinCommand) error
Enroll(udid string) (*device.Profile, error)
}
// NewCheckinService creates a new MDM Checkin Service
@@ -121,9 +123,21 @@ func (svc mdmCheckinService) Checkout(cmd mdm.CheckinCommand) error {
return err
}
existing.Enrolled = boolPtr(false)
err = svc.db.SaveDevice(existing)
if err != nil {
return err
}
return nil
}
func (svc mdmCheckinService) Enroll(udid string) (*device.Profile, error) {
profile, err := svc.db.GetProfileForDevice(udid)
if err != nil {
return nil, err
}
return profile, nil
}
// return a pointer to a boolean
func boolPtr(b bool) *bool {
return &b
@@ -141,5 +155,18 @@ func ServiceHandler(ctx context.Context, svc MDMCheckinService) http.Handler {
decodeMDMCheckinRequest,
encodeResponse,
)
return checkinHandler
enroll := makeEnrollmentEndpoint(svc)
enrollmentHandler := httptransport.NewServer(
ctx,
enroll,
decodeMDMEnrollmentRequest,
enrollResponse,
)
r := mux.NewRouter()
r.Methods("PUT").Path("/mdm/checkin").Handler(checkinHandler)
r.Methods("POST").Path("/mdm/checkin").Handler(enrollmentHandler)
return r
}

View File

@@ -3,6 +3,7 @@ package device
import (
"errors"
"fmt"
"io/ioutil"
"os"
"time"
@@ -36,6 +37,10 @@ type Device struct {
Enrolled *bool `json:"enrolled,omitempty" db:"mdm_enrolled,omitempty"`
}
// Profile is an Enrollment profile.
// For now we just have one.
type Profile []byte
// Datastore manages interactions of devices in a database
type Datastore interface {
AddDevice(*Device) error
@@ -43,6 +48,7 @@ type Datastore interface {
SaveDevice(*Device) error
// RemoveDevice() error
// AllDevices() DeviceList,error
GetProfileForDevice(udid string) (*Profile, error)
}
type config struct {
@@ -173,6 +179,17 @@ func (db pgDatastore) SaveDevice(dev *Device) error {
return nil
}
// GetProfileForDevice returns an enrollment profile for a specific device
// For now there's a single profile stored on disk
func (db pgDatastore) GetProfileForDevice(uuid string) (*Profile, error) {
data, err := ioutil.ReadFile("data/profiles/Enrollment.mobileconfig")
if err != nil {
return nil, err
}
profile := Profile(data)
return &profile, nil
}
func migrate(db *sqlx.DB) {
schema := `
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

View File

@@ -119,7 +119,7 @@ func main() {
// router
r := mux.NewRouter()
r.Methods("PUT").Path("/mdm/checkin").Handler(checkinHandler)
r.Methods("PUT", "POST").Path("/mdm/checkin").Handler(checkinHandler)
r.Methods("PUT").Path("/mdm/connect").Handler(connectHandler)
r.Handle("/mdm/commands", commandHandler)
r.Methods("POST").Path("/mdm/commands").Handler(commandHandler)