NanoMDM is a minimalist Apple MDM server heavily inspired by MicroMDM

NanoMDM

Go

NanoMDM is a minimalist Apple MDM server heavily inspired by MicroMDM.

Getting started & Documentation

  • Quickstart
    A quick guide to get NanoMDM up and running using ngrok.

  • Operations Guide
    A brief overview of the various command-line switches and HTTP endpoints and APIs available to NanoMDM.

Features

  • Horizontal scaling: zero/minimal local state. Persistence in storage layers. MySQL backend provided in the box.
  • Multiple APNs topics: potentially multi-tenant.
  • Multi-command targeting: send the same command (or pushes) to multiple enrollments without individually queuing commands.
  • Migration endpoint: allow migrating MDM enrollments between storage backends or (supported) MDM servers
  • Otherwise we share many features between MicroMDM and NanoMDM, such as:
    • A MicroMDM-emulating HTTP webhook/callback.
    • Enrollment-certificate authorization
    • API-driven interaction (queuing of commands, APNs pushes, etc.)

$x not included

NanoMDM is but one component for a functioning MDM server. At a minimum you need a SCEP server and TLS termination, for example. If you've used MicroMDM before you might be interested to know what NanoMDM does not include, by way of comparison.

  • SCEP.
    • Spin up your own scep server. Or bring your own.
  • TLS.
    • You'll need to provide your own reverse proxy/load balancer that terminates TLS.
  • ADE (DEP) API access.
    • While ADE/DEP enrollments are supported there is no DEP API access.
  • Enrollment (Profiles).
    • You'll need to create and serve your own enrollment profiles to devices.
  • Blueprints.
    • No 'automatic' command sending upon enrollment. Entirely driven my webhook or other integrations.
  • JSON command API.
    • Commands are submitted in raw Plist form only. See the cmdr.py tool that helps generate raw commands
    • The micro2nano project provides an API translation server between MicroMDM's JSON command API and NanoMDM's raw Plist API.
  • VPP.
  • Enrollment (device) APIs.
    • No ability, yet, to inspect enrollment details or state.
    • This is partly mitigated by the fact that both the file and mysql storage backends are "easy" to inspect and query.

Architecture Overview

NanoMDM, at its core, is a thin composable layer between HTTP handlers and a set of storage abstractions.

  • The "front-end" is a set of standard Golang HTTP handlers that handle MDM and API requests. The core MDM handlers adapt the requests to the service layer. These handlers exist in the http package.
  • The service layer is a composable interface for processing and handling MDM requests. The main NanoMDM service dispatches to the storage layer. These services exist under the service package.
  • The storage layer is a set of interfaces and implementations that store & retrieve MDM enrollment and command data. These exist under the storage package.

You can read more about the architecture in the blog post Introducing NanoMDM.

Owner
MicroMDM
macOS MDM and related services
MicroMDM
Comments
  • Update pkcs7 library as a fix for ber2der error

    Update pkcs7 library as a fix for ber2der error

    What's up?

    Been getting the occasional error for certain unlucky enrollments with certain Mdm-Signature.

    err=ber2der: BER tag length is more than available data
    

    When this happens, PUT /mdm returns a 400 response.

    The fix?

    Not sure, but after upgrading the PKCS7 library, replaying the same request works. Maybe it's this? https://github.com/mozilla-services/pkcs7/pull/61

    Regardless, reproduced that error happens in pkcs7.Parse() in main and upgrading the library solved it.

  • PostgreSQL storage

    PostgreSQL storage

    PostgreSQL storage added. Based on mysql storage package. Much of the code is duplicated, but as Go-authors said, sometimes a little more copy-paste is better than a little more dependencies. github.com/lib/pq - is used as db driver.

  • Accomodate large command results

    Accomodate large command results

    Hello!

    When handling the command result response for InstalledApplicationList, nanomdm issues the following plea:

    level=info handler=checkin-command msg=command report results err=storing command report: Error 1406: Data too long for column 'result' at row 1
    

    The InstalledApplicationList response for my current device - with only base Apple apps - is 117,465 characters of base64-endcoded text. TEXT support 65,525 characters; MEDIUMTEXT, 16,777,215 (this corresponds to ~16.78 mb: "L + 3 bytes, where L < 2^24" - https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html#data-types-storage-reqs-strings)

    This works with current nanomdm release (in as much as I migrated with schema.00005.sql and everything continued to work/InstalledApplicationList started working). MySQL docs do not have any warnings regarding migrating between text types that I found.

  • Context logging

    Context logging

    Adds a simple ctxlog package which permits associating multiple CtxKVFuncs with a context. Then, when it's time to log, these are ran using the request context to pull out logging key-value pairs and create a new logger.

    The end result is something like this for a typical NanoMDM push request.

    Before:

    2022/03/16 14:03:09 level=info handler=log addr=::1 method=GET path=/v1/push/D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8 agent=curl/7.64.1
    2022/03/16 14:03:09 level=info service=push msg=retrieved push cert topic=com.apple.mgmt.External.deadbeef-1f17-4c8e-8a63-dd17d3dd35d9
    2022/03/16 14:03:09 level=debug handler=push msg=push count=1 errs=0
    2022/03/16 14:03:09 level=info handler=log addr=192.168.0.2 method=PUT path=/mdm agent=MDM-OSX/1.0 mdmclient/1455 real_ip=2301:602:8a02:9b0f:422:25a8:481b:bd42
    2022/03/16 14:03:09 level=info service=nanomdm status=Idle id=D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8 type=Device
    2022/03/16 14:03:09 level=debug service=nanomdm msg=no command retrieved id=D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8
    

    After:

    2022/03/16 14:02:44 level=info handler=log trace_id=84172eff419691ca addr=::1 method=GET path=/v1/push/D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8 agent=curl/7.64.1
    2022/03/16 14:02:44 level=info service=push trace_id=84172eff419691ca msg=retrieved push cert topic=com.apple.mgmt.External.deadbeef-1f17-4c8e-8a63-dd17d3dd35d9
    2022/03/16 14:02:44 level=debug handler=push trace_id=84172eff419691ca msg=push count=1 errs=0
    2022/03/16 14:02:44 level=info handler=log trace_id=c35015253617eb34 addr=192.168.0.2 method=PUT path=/mdm agent=MDM-OSX/1.0 mdmclient/1455 real_ip=2301:602:8a02:9b0f:422:25a8:481b:bd42
    2022/03/16 14:02:45 level=info service=nanomdm trace_id=c35015253617eb34 id=D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8 type=Device status=Idle
    2022/03/16 14:02:45 level=debug service=nanomdm trace_id=c35015253617eb34 id=D7083AF3-DCCB-5661-A678-BCE8F4D9A2C8 type=Device msg=no command retrieved
    

    Note the trace_id value which tracks each log's request from its origin. Note also that id and type in the service=nanomdm layer is also a context value rather than explicitly logged now.

  • Cutting a release?

    Cutting a release?

    Hi, there's been a lot of improvements since the last release in June 2021.

    As much as we can, we'd like to rely on official releases. Was wondering:

    1. Can we get a release cut? 😸
    2. Maybe it's good to discuss what do releases mean for NanoMDM and if it makes sense to have a certain type of cadence of cutting releases?

    e.g. I can imagine if there was a time or feature based release schedule that followed some level of semantic versioning in spirit, we would try our best frequently update to the nearest minor version and run some form of QA process before a major / breaking version change.

  • stdlogfmt (default logger) changes

    stdlogfmt (default logger) changes

    Optionize stdlogfmt package/New, add filename and line logging, use timestamp format.

    Output looks like:

    ts=2022-07-21T08:41:40-07:00 level=info msg=storage setup storage=pgsql caller=cli.go:60
    ts=2022-07-21T08:41:40-07:00 level=info msg=storage setup storage=mysql caller=cli.go:60
    ts=2022-07-21T08:41:40-07:00 level=info msg=storage setup storage=file caller=cli.go:60
    ts=2022-07-21T08:41:40-07:00 level=info msg=storage setup storage=multi-storage count=3 caller=cli.go:93
    ts=2022-07-21T08:41:40-07:00 level=debug msg=declarative management setup url=http://127.0.0.1:5000/ caller=main.go:102
    ts=2022-07-21T08:41:40-07:00 level=info service=certauth msg=allowing retroactive associations caller=certauth.go:95
    ts=2022-07-21T08:41:40-07:00 level=info msg=starting server listen=:9000 caller=main.go:208
    
  • HTTP codes (well, 500) for API responses

    HTTP codes (well, 500) for API responses

    In lieu of parsing responses for push_error etc, I would prefer to rely on the HTTP code to judge success. This is a bit awkward when enqueuing commands for multiple IDs, as not all may have failed. I argue to that one failure should return 500 for the entire operation - if the client sent multiple IDs, it is the client's responsibility to read the json response and discover the precise error.

    I didn't get fancy with specific error codes, willing to though.

    I'm a go noob, so I apologize for any glaring mistake, bad pattern, etc. Please let me know!

  • Attempt to make Quickstart easier to follow

    Attempt to make Quickstart easier to follow

    Found the two ngrok urls to be hard to grok :), have to keep going back to see if that was the SCEP one or nanomdm one. So I thought this adding the prefix makes it easier to follow.

    Also, ngrok supports multiple tunnels on one process on the free plan, but it's easy to overlook. Added a line on configuring multiple tunnels.

  • Mode to recapture client certificate

    Mode to recapture client certificate

    By default NanoMDM will save the client identity certificate for a device enrollment during the Authenticate check-in message (for both the file and mysql backends). However if enrollments happened in such a way that client certificates were missed (say, via migration) then we should support a way to "recapture" client certificates in storage by saving the identity certificate. It would be wasteful to do this on every request so perhaps it would be only be turned on with a flag.

  • add github action for mysql backend

    add github action for mysql backend

    Currently the MySQL backend is not tested, as it requires MySQL server to run. I have added an integration test to the GitHub workflows which starts MySQL server in a container, which allows the backend to be tested. This ignores several "issues" that exist with the tests, such as the fact that the test is non-idempotent, and fails to run twice.

  • Error trying to enroll a iOS device

    Error trying to enroll a iOS device

    Hi! I'm new to nanoMDM, I was trying to set it up for testing and followed the steps on quickstart.md. When the server was up and running i opened safari on my iphone and went to '{my_local_ip}/mdm' to enroll the device, and then the server responded with a bad request and the logs look like this

    2022/09/07 08:49:02 level=info handler=log addr=192.168.0.90 method=GET path=/mdm agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Mobile/15E148 Safari/604.1
    2022/09/07 08:49:02 level=debug handler=cert-extract msg=empty Mdm-Signature header
    2022/09/07 08:49:02 level=info handler=cert-verify msg=error verifying MDM certificate err=missing MDM certificate
    

    Am I doing something wrong?

    The command I'm running is ./nanomdm-darwin-amd64 -ca ca.pem -api nanomdm -debug

  • Simplify MySQL delete statement

    Simplify MySQL delete statement

    depends on #58

    Currently the delete statement in the MySQL backend is a complicated multiple-table statement, which is a proprietary MySQL extension to the SQL language. Furthermore, we have observed issues with this delete statement deadlocking with itself (when two statements run for the same command row) due to lock-upgrading issues that occur between the query and delete steps.

    I have changed this DELETE statement to be an ordinary delete, with two simple select statements (with no joins). These select statements may not have to be annotated FOR UPDATE, but I wanted to err on the side of avoiding deadlocks.

    Testing:

    • Observe that MySQL integration tests pass TODO:
    • Make sure that deadlocks no longer occur.
  • remove view from mysql backend

    remove view from mysql backend

    The view_queue is not materialized, and thus functions as syntactic sugar for a complex query. Furthermore, our infrastructure works poorly with views. Therefore, this usage adds a significant maintainership burden for little gain.

    This commit removes the view from NanoMDM code paths, but it still exists in the schema. This is purely for convenience and interactive users.

    Furthermore, I added an index used by the queue query, which should have existed for the view. This should improve performance.

    Testing:

    • Tested migration - see the schema migration script
    • Tested base functionality
    • Passes new integration test

    depends on #58 fixes #53 for mysql backend

  • MongoDB Storage Capabilities

    MongoDB Storage Capabilities

    Enabling mongoDB storage support and integrating it into the cli startup capabilities

    Note: The feature TODOs are not mission critical to the daily operation

    Happy to support as necessary going forward 😀

  • SQL backends: remove VIEWs from code

    SQL backends: remove VIEWs from code

    As discussed in #3 would like to move away from direct VIEW usage for the command queue in the storage backend. We can keep it in the schema.sql files as a helper for folks, but would like to move to direct table query in the code.

    The MySQL backend:

    https://github.com/micromdm/nanomdm/blob/9ca3cacf2bb5deb8c2d9cc9d69985ebec0f78176/storage/mysql/queue.go#L148

    The Postgres backend:

    https://github.com/micromdm/nanomdm/blob/9ca3cacf2bb5deb8c2d9cc9d69985ebec0f78176/storage/pgsql/queue.go#L162

  • Publish Docker image

    Publish Docker image

    Since there is already a Dockerfile in the repo and its actively using GitHub Actions, it would be great to publish a container to Docker Hub or the Github Container registry for easy access. Personally it would make creating a helm chart much easier if I don't have to build this locally.

Related tags
⚡ HTTP/2 Apple Push Notification Service (APNs) push provider for Go — Send push notifications to iOS, tvOS, Safari and OSX apps, using the APNs HTTP/2 protocol.

APNS/2 APNS/2 is a go package designed for simple, flexible and fast Apple Push Notifications on iOS, OSX and Safari using the new HTTP/2 Push provide

Jan 1, 2023
🔊Minimalist message bus implementation for internal communication

?? Bus Bus is a minimalist event/message bus implementation for internal communication. It is heavily inspired from my event_bus package for Elixir la

Jan 3, 2023
:notes: Minimalist websocket framework for Go
:notes: Minimalist websocket framework for Go

melody ?? Minimalist websocket framework for Go. Melody is websocket framework based on github.com/gorilla/websocket that abstracts away the tedious p

Dec 30, 2022
Memlog - A Kafka log structure inspired in-memory and append-only data structure

Benchmark with log size 1000 go test -bench=. -cpu 1,2,4,8,16 -benchmem goos: darwin goarch: amd64 pkg: github.com/embano1/memlog cpu: Intel(R) Core(T

Dec 25, 2022
Scalable real-time messaging server in language-agnostic way
Scalable real-time messaging server in language-agnostic way

Centrifugo is a scalable real-time messaging server in language-agnostic way. Centrifugo works in conjunction with application backend written in any

Jan 2, 2023
golang long polling library. Makes web pub-sub easy via HTTP long-poll server :smiley: :coffee: :computer:
golang long polling library.  Makes web pub-sub easy via HTTP long-poll server :smiley: :coffee: :computer:

golongpoll Golang long polling library. Makes web pub-sub easy via an HTTP long-poll server. New in v1.1 Deprecated CreateManager and CreateCustomMana

Jan 6, 2023
Golang push server cluster
Golang push server cluster

gopush-cluster gopush-cluster is a go push server cluster. Features light weight high performance pure golang implementation message expired offline m

Dec 28, 2022
A push notification server written in Go (Golang).
A push notification server written in Go (Golang).

gorush A push notification micro server using Gin framework written in Go (Golang) and see the demo app. Contents gorush Contents Support Platform Fea

Jan 8, 2023
websocket based messaging server written in golang

Guble Messaging Server Guble is a simple user-facing messaging and data replication server written in Go. Overview Guble is in an early state (release

Oct 19, 2022
Server-sent live updates: protocol and reference implementation
Server-sent live updates: protocol and reference implementation

Protocol and Reference Implementation Mercure is a protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast

Jan 1, 2023
Uniqush is a free and open source software system which provides a unified push service for server side notification to apps on mobile devices.

Homepage Download Blog/News @uniqush Introduction Uniqush (\ˈyü-nə-ku̇sh\ "uni" pronounced as in "unified", and "qush" pronounced as in "cushion") is

Jan 9, 2023
⚡️ A lightweight service that will build and store your go projects binaries, Integrated with Github, Gitlab, Bitbucket and Bitbucket Server.
⚡️ A lightweight service that will build and store your go projects binaries, Integrated with Github, Gitlab, Bitbucket and  Bitbucket Server.

Rabbit A lightweight service that will build and store your go projects binaries. Rabbit is a lightweight service that will build and store your go pr

Nov 19, 2022
High-Performance server for NATS, the cloud native messaging system.
High-Performance server for NATS, the cloud native messaging system.

NATS is a simple, secure and performant communications system for digital systems, services and devices. NATS is part of the Cloud Native Computing Fo

Jan 2, 2023
Notifie Server is a learning list notifier

Notifie Server Notifie Server is a learning list notifier. E.g; If you want to l

Dec 24, 2021
Converter EPG XMLTV to OTT-play (by Alex) server side JSON

EPG converter for OTT-play FOSS Описание Инструмент создания телепрограммы для OTT-Play FOSS, использует 1 поток, и буферное чтение из файла, что позв

Jan 6, 2023
A brief demo of real-time plotting with Plotly, Go, and server-sent events
A brief demo of real-time plotting with Plotly, Go, and server-sent events

Golang SSE Demo A brief demo of real-time plotting with Plotly, Go, and server-side events. Overview I first learned about Server-Sent Events from @mr

Nov 28, 2022
mdmb is a tool for simulating Apple devices interacting with Apple MDM servers.

mdmb mdmb — short for MDM Benchmark, à la ab — is a tool for simulating Apple devices interacting with Apple MDM servers. mdmb creates sets of fake Ap

Dec 1, 2022
Sekura is an Encryption tool that's heavily inspired by the Rubberhose file system.

It allows for multiple, independent file systems on a single disk whose existence can only be verified if you posses the correct password.

Oct 16, 2022
A standalone nREPL/prepl client written in Go and heavily inspired by Grenchman

Trenchman A standalone nREPL/prepl client written in Go, heavily inspired by Grenchman Trenchman is a standalone nREPL/prepl client, which means that

Dec 11, 2022
xlsxlang is a tiny toy script programming language. xlsxlang is heavily inspired by Lisp
xlsxlang is a tiny toy script programming language. xlsxlang is heavily inspired by Lisp

xlsxlang Table of Contents 1. Usage 1.1. Examples 2. Installation 3. Supported functions 4. LICENSE xlsxlang is a tiny toy script programming language

Feb 11, 2022