NanoMDM Documentation

repository·main·Indexed 20 days ago

https://github.com/micromdm/nanomdm

A minimalist Apple MDM server and library designed for horizontal scalability and composability. NanoMDM acts as a thin layer between HTTP handlers and storage backends such as MySQL, PostgreSQL, and filekv. It features multi-tenancy support, efficient command targeting, and MicroMDM compatibility. The project includes the nano2nano CLI tool for migrating MDM check-ins between instances and supports API-driven interaction for queuing commands and APNs pushes.

Tokens
8.3K
Snippets
24
Records
36
Agent score
70%

What's inside NanoMDM

  1. Key features of NanoMDM

    main

    NanoMDM provides several core capabilities for MDM management:

    • Horizontal Scaling: Minimal local state allows for scaling; persistence is handled by MySQL or PostgreSQL backends.
    • Multi-tenancy Support: Supports multiple APNs topics.
    • Efficient Command Targeting: Send a single command or push to multiple enrollments simultaneously without individual queuing.
    • Migration Support: Includes a migration endpoint to move enrollments between different storage backends or supported MDM servers.
    • MicroMDM Compatibility: Provides a MicroMDM-emulating HTTP webhook/callback and supports enrollment-certificate authorization.
    • API-driven Interaction: Full API support for queuing commands and APNs pushes.
  2. Configure an MDM enrollment profile

    main

    To enroll devices, you must author a .mobileconfig profile containing specific NanoMDM and SCEP values:

    • SCEP Payload URL: The public HTTPS URL of your SCEP server.
    • SCEP Challenge: The challenge string configured on your SCEP server.
    • MDM Server URL: The public HTTPS URL of your NanoMDM server, typically ending in /mdm (e.g., https://<ngrok-url>/mdm).
    • APNS Topic: The topic returned by NanoMDM when you uploaded the push certificate.
  3. Verify command delivery and client check-in

    main

    After sending a command via the /v1/enqueue/{device_id} endpoint, you can verify the lifecycle of the command by monitoring the NanoMDM server logs. A successful round trip follows this pattern:

    1. Command Enqueued: The server logs an enqueue event with the command_uuid and request_type.
    2. Push Sent: The server logs a push event indicating the notification was sent.
    3. Client Check-in: The client performs a PUT request to the /mdm endpoint.
    4. Command Retrieval: The server logs command retrieved for the specific command_uuid.
    5. Acknowledgment: The client's status changes to Acknowledged for that command.

    If the logs show no command retrieved immediately after an acknowledgment, it typically indicates the client has successfully processed the command and is checking in for new instructions.

  4. Understand NanoMDM Enrollment IDs

    main

    NanoMDM simplifies Apple MDM's complex identifier system (Device vs. User channels, UDID, EnrollmentID, UserID) by collapsing them into a single, normalized "enrollment ID" string. This single ID is used for targeting commands and pushes to devices. NanoMDM resolves various identifiers to their specific channel and enrollment types and then normalizes them into a consistent format.

    Common enrollment ID formats include:

    • macOS Device: A standard UUID.
    • macOS User: A combination of UUID:UUID.
    • iOS Device: A standard UUID.
    • iOS User Enrollment (Device): A standard UUID.
    • iOS Shared iPad: A combination of UUID:ShortName (e.g., UUID:appleid@example.com).
    | Type    | Platform | ID Normalized | ID Example |
    | ------- | -------- | ------------- | ------- |
    | Device  | macOS    | `UUID`        | `470E005B-17C1-4537-BBB3-0EBC340D432A` |
    | User    | macOS    | `UUID:UUID`   | `470E005B-17C1-4537-BBB3-0EBC340D432A:F151140B-3988-45A9-9471-E96B49F27D93` |
    | Device  | iOS      | `UUID`        | `8b3b8ba3783e9ade1dae4fbb944ab3afc0ce5b69` |
    | User Enrollment (Device) | iOS | `UUID` | `b318edb72b556059a013368e3150050c5f74a2c6` |
    | Shared iPad | iOS  | `UUID:ShortName` | `68656c6c6f776f726c6468656c6c6f776f726c64:appleid@example.com` |
  5. Understand NanoMDM architecture

    main

    NanoMDM is designed as a thin, composable layer between HTTP handlers and storage abstractions. The architecture is divided into three primary layers:

    1. Front-end (http package): A set of standard Golang HTTP handlers that manage MDM and API requests. These handlers adapt incoming requests for the service layer.
    2. Service Layer (service package): A composable interface that processes and handles MDM requests. The main NanoMDM service dispatches logic from here to the storage layer.
    3. Storage Layer (storage package): A set of interfaces and implementations responsible for storing and retrieving MDM enrollment and command data. Supported backends include MySQL and PostgreSQL.

    This design enables horizontal scaling because the server maintains minimal local state, offloading persistence to the storage layer.

  6. Required external components for NanoMDM

    main

    NanoMDM is a core MDM component but is not a complete MDM solution. To function in a production environment, you must provide the following:

    • SCEP Server: NanoMDM does not include SCEP; you must run your own (e.g., using the scep project) or bring your own.
    • TLS Termination: You must provide a reverse proxy or load balancer to handle TLS termination.
    • Enrollment Profiles: You are responsible for creating and serving your own enrollment profiles to devices.
    • Command Submission: NanoMDM accepts commands in raw Plist form only. It does not have a native JSON command API.
      • To assist with this, use the cmdr.py tool located in tools/cmdr.py to generate raw commands.
      • Alternatively, use the micro2nano project to act as a translation server between MicroMDM's JSON API and NanoMDM's Plist API.
  7. Use the Authentication Proxy for MDM-authenticated content

    main

    If the -auth-proxy-url flag is set, any request to a URL starting with /authproxy/ will be reverse-proxied to the target URL.

    This feature allows for MDM-authenticated content retrieval (useful for Declarative Device Management 'Asset' declarations). NanoMDM performs the same authentication checks on the proxy request as it does for standard MDM endpoints (including TLS client configuration and -cert-header handling).

    Example: If -auth-proxy-url is http://[::1]:9008, a request to /authproxy/foo/bar is proxied to http://[::1]:9008/foo/bar.

  8. Set up a SCEP server for NanoMDM

    main

    NanoMDM requires a SCEP (Simple Certificate Enrollment Protocol) server to issue device identity certificates. You can use MicroMDM's SCEP server.

    1. Initialize the CA: Use the ca -init command to create a new Certificate Authority. By default, data is stored in a depot directory.
    2. Run the server: Start the server using the binary. You can specify a port with the -port switch. Use -allowrenew 0 and -challenge <string> to configure renewal and challenge settings.
    3. Expose via Proxy: If running locally, use a tool like ngrok to provide a public HTTPS URL for the SCEP service.
    # Initialize SCEP CA
    ./scepserver-darwin-amd64 ca -init
    
    # Run SCEP server
    ./scepserver-darwin-amd64 -allowrenew 0 -challenge nanomdm -debug
  9. Install and run NanoMDM

    main

    You can deploy NanoMDM using several methods depending on your environment:

    Using Docker

    Pull and run the latest official image from GHCR.io:

    docker pull ghcr.io/micromdm/nanomdm:latest
    docker run ghcr.io/micromdm/nanomdm:latest

    Using Go (from source)

    If you have the Go toolchain installed, you can build the server directly from the source repository using make.

    Using Release Binaries

    Download the pre-compiled .zip files containing the server and necessary supplementals from the GitHub releases page.

  10. Migrate enrollments using nano2nano

    main

    The nano2nano tool is used to migrate enrollment data between NanoMDM storage backends (e.g., from file to mysql).

    Key Constraints

    • Lossy Migration: This is not a full backup/restore. It only migrates the absolute minimum data required for a device to operate (APNs push topic, token, and push magic). Previous commands, responses, and inventory data are not migrated.
    • Server URL: The target NanoMDM server must have the exact same ServerURL as the source for migrations to work.
    • iOS Unlock Tokens: If the latest TokenUpdate did not contain the enroll-time unlock token for iOS, that information will be lost.

    Usage

    Run the tool with the source storage details and the target migration endpoint URL.

    $ ./nano2nano-darwin-amd64 -storage file -storage-dsn db -url 'http://127.0.0.1:9010/migration' -key nanomdm -debug
  11. Run the NanoMDM server

    main

    Start the NanoMDM server using the following requirements:

    • -ca <path>: Path to the SCEP CA certificate used to validate device identity certificates.
    • -api <key>: Sets an API key for HTTP Basic Authorization. The username is always nanomdm.
    • -debug: Enables debug logging.

    By default, NanoMDM uses a file storage backend and writes enrollment data into a db directory.

    ./nanomdm-darwin-amd64 -ca ca.pem -api nanomdm -debug
  12. Generate and send MDM commands using cmdr.py

    main

    NanoMDM includes a command generation tool, cmdr.py, which can be used to create valid MDM command payloads (in XML/plist format). This is useful for testing the server's ability to enqueue and push commands to devices.

    Generate a specific command

    To generate a specific command type, such as SecurityInfo, run the script with the command name as an argument:

    ./tools/cmdr.py SecurityInfo

    Send a random command to a device

    You can use the -r flag to pick a random read-only command and pipe it directly to the NanoMDM enqueue API using curl.

    Endpoint: PUT /v1/enqueue/{device_id} Authentication: Basic Auth (username:password)

    To send a command to a specific device ID, use the following pattern:

    ./tools/cmdr.py -r | curl -T - -u nanomdm:nanomdm 'http://127.0.0.1:9000/v1/enqueue/DEVICE_ID'

    Omit Push Notifications

    If you are sending multiple commands and want to avoid triggering a push notification for every single one, append the &no_push=1 URL parameter to the request URL.

    ./tools/cmdr.py -r | curl -T - -u nanomdm:nanomdm 'http://127.0.0.1:9000/v1/enqueue/E9085AF6-DCCB-5661-A678-BCE8F4D9A2C8'