Skip to content

Commit

Permalink
Refactor DM to be pluggable
Browse files Browse the repository at this point in the history
  • Loading branch information
jessepeterson committed Sep 30, 2021
1 parent cb341fb commit 536c945
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 67 deletions.
4 changes: 3 additions & 1 deletion cmd/nanomdm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)

Expand Down
86 changes: 86 additions & 0 deletions service/nanomdm/dm.go
Original file line number Diff line number Diff line change
@@ -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
}
68 changes: 3 additions & 65 deletions service/nanomdm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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
}
8 changes: 7 additions & 1 deletion service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down

0 comments on commit 536c945

Please sign in to comment.