Tinkering with Declarative Management



Reading Time: 7 minutes

Amongst the many announcements at Apple’s WWDC21 was declarative management. I would highly recommend watching the WWDC session “Meet declarative device management” to get a a general idea of what it is and how it works.

I wanted to tinker with the new capabilities and created some initial support for it and wanted to share how you can try this out, too. My hope is that, if you’re inclined, you can get these tools and start playing with it.

Note this is a mostly technical how-to to get up and running and testing Declarative Management with NanoMDM. I hope to write more about the conceptual/paragidm changes DM brings as well more broad thinking in how to implement this protocol in a server. See the bottom for links to further discussion. Also note this is all really early stuff, based on beta software and documentation priovided by Apple and of course is all subject to major breaking changes.

Requirements

First you’ll need to have some things to get this going.

Putting it all together this means that this iOS/iPadOS 15.0 device needs to be enrolled into the the NanoMDM instance using User Enrollment. NanoMDM will let you know the style of enrollment of the device when it connects (as confirmation).

Overview

If you look at the publically available documentation you can see that the DeclarativeManagement check-in is, essentially, a wrapper for an HTTP service. It contains a body (called Data) and a “URL” endpoint (called Endpoint).

To that end NanoMDM simply treats it as such and synchronously dispatches to an external HTTP DM “server” with that HTTP data. We return the HTTP result to the MDM client in the body of the HTTP DeclarativeManagement check-in from this DM “server” — not as a Plist (like other check-ins) but as whatever the DM “server” responds with: usually nothing or JSON results.

Note that we also include the NanoMDM enrollment ID in the request in the X-Enrollment-ID header.

Declarative management “server”

Before we setup NanoMDM we’ll setup the toy Declarative Management server. This is a Flask python app that lives in a GitHub Gist.

To get it going you might do something like:

$ python3 -m venv dm-venv
$ source dm-venv/bin/activate
$ pip install --upgrade pip
$ pip install flask
Collecting flask
[..snip..]
$ git clone https://gist.github.com/jessepeterson/5a633f627bfc23f74153add89aee07f1 dm
Cloning into 'dm'...
[..snip..]
$ cd dm
$ export FLASK_APP=app
$ export FLASK_DEBUG=1
$ flask run
[..snip..]
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[..snip..]

Note the “running on” line. We’ll point NanoMDM at this server in a sec.

NanoMDM branch

As this feature is very new this code only exists in a Pull Request against NanoMDM at the moment. So you’ll have to check that specific branch and build it. That might look like this (this assumes you have the Go compiler set installed):

$ mkdir -p ~/go/src/github.com/micromdm/nanomdm
$ cd ~/go/src/github.com/micromdm
$ git clone git@github.com:jessepeterson/nanomdm.git
Cloning into 'nanomdm'...
[..snip..]
$ cd nanomdm
$ git checkout declarative-management
Branch declarative-management set up to track remote branch declarative-management from origin.
Switched to a new branch 'declarative-management'
$ make
[..snip..]

You should now have a binary of NanoMDM from the declarative-management branch in the jessepeterson fork of NanoMDM. This branch has a new switch: -dm which just takes a URL to the DM “server”. This means our NanoMDM invocation would be something like:

./nanomdm-darwin-amd64 [normal-nanomdm-switches] -dm http://127.0.0.1:5000/ -dump

The -dump switch just tells NanoMDM to print out everything for us so we can inspect how the DM command runs.

Activating declarative management

Once you’ve done all this, then you can active Declarative Management by sending your device the DeclarativeManagement command. With NanoMDM that looks like (from a check-out of NanoMDM):

./tools/cmdr.py command DeclarativeManagement | curl -v -T - -u nanomdm:api-key '[::1]:9000/v1/enqueue/B367CA4B-A874-DDA6-8168-A6295B54C7DF'

At this point assuming everything is working (and it is a lot of moving parts) you should see the Check-in requests for DeclarativeManagement.

2021/06/10 12:51:05 level=info service=nanomdm msg=DeclarativeManagement id=B367CA4B-A874-DDA6-8168-A6295B54C7DF type=User Enrollment (Device) endpoint=declaration-items

Here we see that NanoMDM printed the enrollment ID, the check-in msg (DeclarativeManagement), it’s type (User Enrollment) and the DM endpoint (declaration-items).

Even cooler this forwared the request over to http://127.0.0.1:5000/declaration-items for processing by appending the endpoint to the DM URL prefix. Our flask should have received this and printed:

127.0.0.1 - - [10/Jun/2021 12:51:05] "GET /declaration-items HTTP/1.1" 200 -

What you should then start seeing is the device doing various things with the actual Declaration Management protocol:

Here’s an example of what this a status update looks like for changing declartions:

{
  "StatusItems" : {
    "management" : {
      "declarations" : {
        "activations" : [
          {
            "active" : true,
            "identifier" : "49F6F16A-70EB-4A89-B092-465FAEC5E550",
            "valid" : "valid",
            "server-token" : "c306a35e-438c-55ca-af4c-e847d7bda0f9"
          }
        ],
        "configurations" : [
          {
            "reasons" : [
              {
                "details" : {
                  "Identifier" : "0FCD2F56-D5BC-48EA-B98D-E0CCC0C6F9E0",
                  "ServerToken" : "539f34e8-74f5-53d9-b986-374d1c4dedbc"
                },
                "description" : "Configuration (0FCD2F56-D5BC-48EA-B98D-E0CCC0C6F9E0:539f34e8-74f5-53d9-b986-374d1c4dedbc) is missing state.",
                "code" : "Error.MissingState"
              }
            ],
            "active" : false,
            "identifier" : "0FCD2F56-D5BC-48EA-B98D-E0CCC0C6F9E0",
            "valid" : "valid",
            "server-token" : "539f34e8-74f5-53d9-b986-374d1c4dedbc"
          },
          {
            "active" : true,
            "identifier" : "0FCD2F56-D5BC-48EA-B98D-E0CCC0C6F9E0",
            "valid" : "valid",
            "server-token" : "93665bad-af46-5391-b800-5483a4b07ebd"
          },
          {
            "active" : true,
            "identifier" : "85B5130A-4D0D-462B-AA0D-0C3B6630E5AA",
            "valid" : "unknown",
            "server-token" : "936e16fd-725f-5534-be1d-4e822ed78264"
          }
        ],
        "assets" : [

        ],
        "management" : [
          {
            "active" : false,
            "identifier" : "AF0E633E-7ADB-4B2A-A48C-1B20AA271D08",
            "valid" : "valid",
            "server-token" : "456fc8c6-85e9-52e9-b93d-eed694a4f487"
          }
        ]
      }
    }
  },
  "Errors" : [

  ]
}

In this example the Error.MissingState is telling us (I think) that one of the declarations is out of date and was retrieved. We’ll get follow-up status updates letting us know each configuration was eventually “valid” or “invalid”, it seems, too.

To get updated/changed items make some trivial change to one of the declarations in the toy server then send another DeclarativeManagement command. The device will check-in, fetch the declaration items, compute any changes, request the changed declarations, try to apply them, and send back statuses.

Note the toy server writes out each status to disk with the enrollment ID and timestamp in the filename.

Feedback & Future

I created a GH Discussion topic under the main MicroMDM site for general discussion about open source MDM and Declarative Management. Hopefully we can coordinate discussion there. For any feedback about the above post (and related tools specifically) I would direct you to the NanoMDM pull-request adding this capability.

I hope this is enough to get you started tinkering with Declarative Managament and NanoMDM. Because of Declarative Management’s limited scope at this time (iOS and iPadOS User Enrollments only) it is almost useless to the community involved in these open source tools (that is: macOS SysAdmin/IT management). But my hope is to get folks familiar ith the protocol and data models so we all can start thinking about future designs to start supporting declarative management when it comes to other platforms/environments.