Add endpoint for inspecting the MDM command queue (#895)

This commit is contained in:
Graham Gilbert
2023-07-12 00:07:42 +01:00
committed by GitHub
parent 7a3715454a
commit c4a1cd97ef
9 changed files with 157 additions and 2 deletions

View File

@@ -2,10 +2,12 @@
- Add `-log-time` flag to include timestamps in log messages (#890)
- Add `-device-signature-skew` flag to allow configuring clock skew when verifying device signatures (#887)
- Tidy code for Go 1.20, and update Go version for Docker and CI
- Tidy code for Go 1.20 and update Go version for Docker and CI (#902)
- Add support for inspecting the MDM command queue (#895)
- See the [docs](https://github.com/micromdm/micromdm/blob/main/docs/user-guide/api-and-webhooks.md#inspecting-the-command-queue) for how to use
- Project dependency updates (#888, #889, #900)
Thanks to our contributors: @jamesez, @korylprince
Thanks to our contributors: @grahamgilbert, @jamesez, @korylprince
## [v1.11.0](https://github.com/micromdm/micromdm/compare/v1.10.1...v1.11.0)

View File

@@ -294,3 +294,27 @@ Authorization: Basic bWljcm9tZG06c3VwZXJzZWNyZXQ=
A helper script is also available at `./tools/api/clear_queue`:
`$ ./clear_queue 55693EB3-DF03-5FD1-9263-F7CDB8AD7FFD`
# Inspecting the Command Queue
[PR #895](https://github.com/micromdm/micromdm/pull/895) added support for inspecting the command queue, which can be useful when diagnosing issues with commands.
Assuming the example command from [Schedule Raw Commands with the API](#schedule-raw-commands-with-the-api) is in the command queue, inspecting the queue looks like:
```
GET /v1/commands/55693EB3-DF03-5FD1-9263-F7CDB8AD7FFD HTTP/1.1
Authorization: Basic bWljcm9tZG06c3VwZXJzZWNyZXQ=
{
"commands": [
{
"uuid": "0001_ProfileList",
"payload": "<base64 encoding of plist command>"
}
]
}
```
A helper script is also available at `./tools/api/inspect_queue`:
`$ ./inspect_queue 55693EB3-DF03-5FD1-9263-F7CDB8AD7FFD`

View File

@@ -36,10 +36,17 @@ type BootstrapTokenRetriever interface {
GetBootstrapToken(ctx context.Context, udid string) ([]byte, error)
}
// Command is an MDM Command
type Command struct {
UUID string `json:"uuid"`
Payload []byte `json:"payload"`
}
// Queue is an MDM Command Queue.
type Queue interface {
Next(context.Context, Response) ([]byte, error)
Clear(context.Context, CheckinEvent) error
ViewQueue(context.Context, CheckinEvent) ([]*Command, error)
}
type MDMService struct {

View File

@@ -11,6 +11,7 @@ type Endpoints struct {
NewCommandEndpoint endpoint.Endpoint
NewRawCommandEndpoint endpoint.Endpoint
ClearQueueEndpoint endpoint.Endpoint
ViewQueueEndpoint endpoint.Endpoint
}
func MakeServerEndpoints(s Service, outer endpoint.Middleware, others ...endpoint.Middleware) Endpoints {
@@ -18,10 +19,19 @@ func MakeServerEndpoints(s Service, outer endpoint.Middleware, others ...endpoin
NewCommandEndpoint: endpoint.Chain(outer, others...)(MakeNewCommandEndpoint(s)),
NewRawCommandEndpoint: endpoint.Chain(outer, others...)(MakeNewRawCommandEndpoint(s)),
ClearQueueEndpoint: endpoint.Chain(outer, others...)(MakeClearQueueEndpoint(s)),
ViewQueueEndpoint: endpoint.Chain(outer, others...)(MakeViewQueueEndpoint(s)),
}
}
func RegisterHTTPHandlers(r *mux.Router, e Endpoints, options ...httptransport.ServerOption) {
// GET /v1/commands/udid View device queue.
r.Methods("GET").Path("/v1/commands/{udid}").Handler(httptransport.NewServer(
e.ViewQueueEndpoint,
decodeViewQueueRequest,
httputil.EncodeJSONResponse,
options...,
))
// POST /v1/commands Add new MDM Command to device queue.
r.Methods("POST").Path("/v1/commands").Handler(httptransport.NewServer(
e.NewCommandEndpoint,

View File

@@ -12,11 +12,13 @@ type Service interface {
NewCommand(context.Context, *mdm.CommandRequest) (*mdm.CommandPayload, error)
NewRawCommand(context.Context, *RawCommand) error
ClearQueue(ctx context.Context, udid string) error
ViewQueue(ctx context.Context, udid string) ([]*mdmsvc.Command, error)
}
// Queue is an MDM Command Queue.
type Queue interface {
Clear(context.Context, mdmsvc.CheckinEvent) error
ViewQueue(context.Context, mdmsvc.CheckinEvent) ([]*mdmsvc.Command, error)
}
type CommandService struct {

54
platform/command/view.go Normal file
View File

@@ -0,0 +1,54 @@
package command
import (
"context"
"net/http"
"github.com/go-kit/kit/endpoint"
"github.com/gorilla/mux"
"github.com/micromdm/micromdm/mdm"
"github.com/pkg/errors"
)
func (svc *CommandService) ViewQueue(ctx context.Context, udid string) ([]*mdm.Command, error) {
commands, err := svc.queue.ViewQueue(
ctx,
mdm.CheckinEvent{Command: mdm.CheckinCommand{UDID: udid}},
)
if err != nil {
return nil, errors.Wrap(err, "clearing command queue")
}
return commands, nil
}
type viewQueueRequest struct {
UDID string
}
type viewQueueResponse struct {
Err error `json:"error,omitempty"`
Commands []*mdm.Command `json:"commands,omitempty"`
}
func (r viewQueueResponse) Failed() error { return r.Err }
func (r viewQueueResponse) StatusCode() int { return http.StatusOK }
func decodeViewQueueRequest(ctx context.Context, r *http.Request) (interface{}, error) {
return viewQueueRequest{UDID: mux.Vars(r)["udid"]}, nil
}
// MakeViewQueueEndpoint creates an endpoint which views device queues.
func MakeViewQueueEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(viewQueueRequest)
if req.UDID == "" {
return viewQueueResponse{Err: errEmptyRequest}, nil
}
commands, err := svc.ViewQueue(ctx, req.UDID)
if err != nil {
return viewQueueResponse{Err: err}, nil
}
return viewQueueResponse{Commands: commands}, nil
}
}

View File

@@ -121,6 +121,30 @@ func (q *QueueInMem) Clear(_ context.Context, event mdm.CheckinEvent) error {
return nil
}
// View returns the command queue for the device in event
func (q *QueueInMem) ViewQueue(_ context.Context, event mdm.CheckinEvent) ([]*mdm.Command, error) {
udid := event.Command.UDID
if event.Command.UserID != "" {
udid = event.Command.UserID
}
if event.Command.EnrollmentID != "" {
udid = event.Command.EnrollmentID
}
l := q.getList(udid)
cmds := make([]*mdm.Command, 0, l.Len())
for item := l.Front(); item != nil; item = item.Next() {
cmd := item.Value.(*queuedCommand)
cmds = append(cmds, &mdm.Command{
UUID: cmd.uuid,
Payload: cmd.payload,
})
}
return cmds, nil
}
func (q *QueueInMem) startPolling(pubsub pubsub.PublishSubscriber) error {
events, err := pubsub.Subscribe(context.TODO(), "command-queue", command.CommandTopic)
if err != nil {

View File

@@ -54,6 +54,33 @@ func (db *Store) Next(ctx context.Context, resp mdm.Response) ([]byte, error) {
return cmd.Payload, nil
}
func (db *Store) ViewQueue(ctx context.Context, event mdm.CheckinEvent) ([]*mdm.Command, error) {
udid := event.Command.UDID
if event.Command.UserID != "" {
udid = event.Command.UserID
}
if event.Command.EnrollmentID != "" {
udid = event.Command.EnrollmentID
}
dc, err := db.DeviceCommand(udid)
if isNotFound(err) {
return nil, nil
} else if err != nil {
return nil, errors.Wrapf(err, "get device commands, udid: %s", udid)
}
cmds := make([]*mdm.Command, len(dc.Commands))
for idx, cmd := range dc.Commands {
cmds[idx] = &mdm.Command{
UUID: cmd.UUID,
Payload: cmd.Payload,
}
}
return cmds, nil
}
func (db *Store) Clear(ctx context.Context, event mdm.CheckinEvent) error {
udid := event.Command.UDID
if event.Command.UserID != "" {

5
tools/api/inspect_queue Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
source $MICROMDM_ENV_PATH
endpoint="v1/commands/$1"
curl $CURL_OPTS -K <(cat <<< "-u micromdm:$API_TOKEN") -X GET "$SERVER_URL/$endpoint"