diff --git a/cmd/nanomdm/main.go b/cmd/nanomdm/main.go index 1425165..3dd0504 100644 --- a/cmd/nanomdm/main.go +++ b/cmd/nanomdm/main.go @@ -92,7 +92,9 @@ func main() { // create 'core' MDM service nanoOpts := []nanomdm.Option{nanomdm.WithLogger(logger.With("service", "nanomdm"))} if *flDMURLPfx != "" { - nanoOpts = append(nanoOpts, nanomdm.WithDeclarativeManagement(*flDMURLPfx)) + logger.Debug("msg", "declarative management setup", "url", *flDMURLPfx) + dm := nanomdm.NewDeclarativeManagementHTTPCaller(*flDMURLPfx) + nanoOpts = append(nanoOpts, nanomdm.WithDeclarativeManagement(dm)) } nano := nanomdm.New(mdmStorage, nanoOpts...) diff --git a/service/nanomdm/dm.go b/service/nanomdm/dm.go new file mode 100644 index 0000000..87343d0 --- /dev/null +++ b/service/nanomdm/dm.go @@ -0,0 +1,86 @@ +package nanomdm + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + + "github.com/micromdm/nanomdm/mdm" + "github.com/micromdm/nanomdm/service" +) + +// DeclarativeManagement Check-in message implementation. Calls out to +// the service's DM handler (if configured). +func (s *Service) DeclarativeManagement(r *mdm.Request, message *mdm.DeclarativeManagement) ([]byte, error) { + if err := s.updateEnrollID(r, &message.Enrollment); err != nil { + return nil, err + } + s.logger.Info( + "msg", "DeclarativeManagement", + "id", r.ID, + "type", r.Type, + "endpoint", message.Endpoint, + ) + if s.dm == nil { + return nil, errors.New("no Declarative Management handler") + } + return s.dm.DeclarativeManagement(r, message) +} + +const enrollmentIDHeader = "X-Enrollment-ID" + +type DeclarativeManagementHTTPCaller struct { + urlPrefix string + client *http.Client +} + +// NewDeclarativeManagementHTTPCaller creates a new DeclarativeManagementHTTPCaller +func NewDeclarativeManagementHTTPCaller(urlPrefix string) *DeclarativeManagementHTTPCaller { + return &DeclarativeManagementHTTPCaller{ + urlPrefix: urlPrefix, + client: http.DefaultClient, + } +} + +func (c *DeclarativeManagementHTTPCaller) DeclarativeManagement(r *mdm.Request, message *mdm.DeclarativeManagement) ([]byte, error) { + if c.urlPrefix == "" { + return nil, errors.New("missing URL") + } + u, err := url.Parse(c.urlPrefix) + if err != nil { + return nil, err + } + u.Path = path.Join(u.Path, message.Endpoint) + method := http.MethodGet + if len(message.Data) > 0 { + method = http.MethodPut + } + req, err := http.NewRequestWithContext(r.Context, method, u.String(), bytes.NewBuffer(message.Data)) + if err != nil { + return nil, err + } + req.Header.Set(enrollmentIDHeader, r.ID) + if len(message.Data) > 0 { + req.Header.Set("Content-Type", "application/json") + } + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return bodyBytes, service.NewHTTPStatusError( + resp.StatusCode, + fmt.Errorf("unexpected HTTP status: %s", resp.Status), + ) + } + return bodyBytes, nil +} diff --git a/service/nanomdm/service.go b/service/nanomdm/service.go index 490b4a5..60f4383 100644 --- a/service/nanomdm/service.go +++ b/service/nanomdm/service.go @@ -2,13 +2,8 @@ package nanomdm import ( - "bytes" - "errors" "fmt" - "io/ioutil" "net/http" - "net/url" - "path" "github.com/micromdm/nanomdm/log" "github.com/micromdm/nanomdm/mdm" @@ -31,8 +26,7 @@ type Service struct { storeRejectedUserAuth bool // Declarative Management - dmURLPrefix string - dmClient *http.Client + dm service.DeclarativeManagement } // normalize generates enrollment IDs that are used by other @@ -70,9 +64,9 @@ func WithLogger(logger log.Logger) Option { } } -func WithDeclarativeManagement(urlPrefix string) Option { +func WithDeclarativeManagement(dm service.DeclarativeManagement) Option { return func(s *Service) { - s.dmURLPrefix = urlPrefix + s.dm = dm } } @@ -82,7 +76,6 @@ func New(store storage.ServiceStore, opts ...Option) *Service { store: store, logger: log.NopLogger, normalizer: normalize, - dmClient: http.DefaultClient, } for _, opt := range opts { opt(nanomdm) @@ -256,58 +249,3 @@ func (s *Service) CommandAndReportResults(r *mdm.Request, results *mdm.CommandRe ) return nil, nil } - -// DeclarativeManagement calls out to an HTTP server with the -// "unwrapped" Declarative Management endpoints. -// -// We append the "Endpoint" key to the provided service DM URL Prefix -// and send along any JSON in the request. If there is any provided Data -// in the check-in message we set the Content-Type to JSON and use HTTP -// PUT instead of GET. -func (s *Service) DeclarativeManagement(r *mdm.Request, message *mdm.DeclarativeManagement) ([]byte, error) { - if err := s.updateEnrollID(r, &message.Enrollment); err != nil { - return nil, err - } - s.logger.Info( - "msg", "DeclarativeManagement", - "id", r.ID, - "type", r.Type, - "endpoint", message.Endpoint, - ) - if s.dmURLPrefix == "" { - return nil, errors.New("missing declarative management URL") - } - u, err := url.Parse(s.dmURLPrefix) - if err != nil { - return nil, err - } - u.Path = path.Join(u.Path, message.Endpoint) - method := http.MethodGet - if len(message.Data) > 0 { - method = http.MethodPut - } - req, err := http.NewRequestWithContext(r.Context, method, u.String(), bytes.NewBuffer(message.Data)) - if err != nil { - return nil, err - } - req.Header.Set("X-Enrollment-ID", r.ID) - if len(message.Data) > 0 { - req.Header.Set("Content-Type", "application/json") - } - resp, err := s.dmClient.Do(req) - if err != nil { - return nil, err - } - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != 200 { - return bodyBytes, service.NewHTTPStatusError( - resp.StatusCode, - fmt.Errorf("unexpected HTTP status: %s", resp.Status), - ) - } - return bodyBytes, nil -} diff --git a/service/service.go b/service/service.go index edaba36..21e5c7a 100644 --- a/service/service.go +++ b/service/service.go @@ -5,6 +5,12 @@ import ( "github.com/micromdm/nanomdm/mdm" ) +// DeclarativeManagement is the interface for handling the Apple +// Declarative Management protocol vi MDM "v1." +type DeclarativeManagement interface { + DeclarativeManagement(*mdm.Request, *mdm.DeclarativeManagement) ([]byte, error) +} + // Checkin represents the various check-in requests. // See https://developer.apple.com/documentation/devicemanagement/check-in type Checkin interface { @@ -14,7 +20,7 @@ type Checkin interface { SetBootstrapToken(*mdm.Request, *mdm.SetBootstrapToken) error GetBootstrapToken(*mdm.Request, *mdm.GetBootstrapToken) (*mdm.BootstrapToken, error) UserAuthenticate(*mdm.Request, *mdm.UserAuthenticate) ([]byte, error) - DeclarativeManagement(*mdm.Request, *mdm.DeclarativeManagement) ([]byte, error) + DeclarativeManagement } // CommandAndReportResults represents the command report and next-command request.