fleet/docs/Contributing/product-groups/mdm/mdm-overview.md

12 KiB

MDM index

Knowledge transfer videos

  • Roberto's MDM knowledge transfer videos

    • Google Drive
    • Also on Loom -- search for MDM knowledge dump
  • Marcos's Windows knowledge transfer videos

Protocol

Apple

Windows

Android

See the Android MDM documentation

Development

  • ./Testing-and-local-development.md has many sections about setting up MDM and debugging specific features
  • tools/mdm/apple/troubleshooting.md has instructions to troubleshoot Apple MDM in hosts
  • tools/mdm/* contains many ad-hoc tools we've been adding to aid development, including but not limited to:
    • Servers to perform MDM migrations from different providers
    • Tools to debug the ABM APIs
    • Tools to import/export certificates and keys from the DB

Fleet specific features

Profiles and Declarations

For Windows, Apple and Android MDM, Fleet defines "profiles" as groups of settings that can be SyncML (Windows), XML (Apple), or JSON (Apple/Android).

Settings are defined per fleet and can be further targeted to hosts using labels.

Profiles and declarations can be uploaded via the UI/CLI by the IT admin. Additionally, Fleet automatically sends profiles to hosts as part of high-level UI actions. For instance, enabling disk encryption in macOS sends the relevant configuration to the host.

To determine the subset of profiles/declarations that should be applied to a specific host, we use the following approach:

  1. Ideal State: The "ideal state" of a host is calculated by combining fleet profiles with any label-based inclusions/exclusions using the mdm_*_configuration_profiles and mdm_apple_declarations tables.
  2. Current State: The "current state" of a host is tracked using the host_mdm_*_configuration_profiles and host_mdm_apple_declarations tables, which are updated based on MDM protocol responses and kept in sync via osquery (commonly referred to as "profile verification" or "double-check").
  3. Diff Calculation: We use set algebra to compute the difference between the ideal and current states—determining the profiles that need to be installed ("to install") and those that should be removed ("to remove").

This logic runs in two main places:

  1. mdm_*_profile_manager Cron Job: Runs every 30 seconds and performs the following actions:

    • Calculates the profiles to install/remove
    • Enqueues the necessary commands
    • Sends push notifications to the hosts
  2. ds.BulkSetPendingMDMHostProfiles: This method is called to mark profiles and declarations as "pending." It's a lighter process than the cron job, only updating database records to reflect pending profiles in the UI, providing immediate feedback to users.

Profile/declaration verification (double check)

Osquery double checks are explained in good detail as part of their initial specs:

NanoMDM Integration for Apple MDM

NanoMDM handles the core protocol operations for Apple MDM. Fleet extends the protocol and adds custom handling to align with our desired workflows.

After NanoMDM processes a protocol operation, it allows for custom logic by implementing the CheckinAndCommandService interface. Our implementation can be found in the service layer.

4ff5f7a18a/server/service/apple_mdm.go (L2595-L2600)

Additionally, per request from management, we maintain a modified version of the NanoMDM repository under server/mdm/nanomdm/ to support Fleet-specific requirements.

MDM Lifecycle

While implementing MDM-specific features, we identified recurring patterns related to managing the "MDM connection" to the host. We refer to this as the "MDM lifecycle." A few examples:

  • The MDM server is not always notified when a host unenrolls. As a result, we perform cleanup during host enrollment since it's impossible to reliably determine whether a host is enrolling for the first time.
  • Special MDM actions are triggered when a host is deleted via the UI.

All lifecycle-related actions are implemented in the HostLifecycle struct. A great way to familiarize yourself with the different MDM actions is to search for the usages of the different HostActions we have.

We use the terminology "turn on" and "turn off" to align with the language used by the product group, which is generally associated with enrollment and unenrollment.

HostLifecycle might not contain all lifecycle events, and does not cover Android as of today (2025/12/22)

SCEP Renewals

MicroMDM has documented the intricacies of SCEP certificate renewals in detail: MicroMDM SCEP Documentation.

In Fleet, we issue certificates with a 1-year validity period. To renew certificates, we send a new enrollment profile via the InstallProfile command.

The command to install the profile is queued in the renew_scep_certificates job, which is part of the cleanups_then_aggregation cron schedule.

It's important to note that when sending a new enrollment profile, certain fields must remain unchanged. Apple has documented these restrictions here: Apple MDM Profile Restrictions.

Puppet module

The Puppet module is deprecated as of Fleet 4.66. It's maintained for backwards compatibility.

The implementation of the Puppet module is described in detail at: ee/tools/puppet/fleetdm/CONTRIBUTING.md

MDM migrations

Windows MDM is more flexible when it comes to switching MDM servers, so MDM migrations are generally not a big deal.

Apple MDM is more strict, and we have built two different flows:

As of MacOS 26, iOS 26, and iPadOS 26, Apple has introduced a native MDM migration flow.

  1. The "regular" flow is what most customers will use, involve fleetd guiding the user through the migration to perform manual steps. The user documentation for this flow is https://fleetdm.com/guides/mdm-migration
  2. The "seamless" flow allows customers with access to their MDM database and ownership of the domain used as the ServerURL in the enrollment profile to migrate the devices without user action. The user documentation for this flow is https://fleetdm.com/guides/seamless-mdm-migration
    1. The proxy for the seamless flow lives in ./tools/mdm/migration/mdmproxy/
    2. The tool to extract data from MicroMDM lives in ./tools/mdm/migration/micromdm/touchless/

Disk encryption

Enabling and retrieving the disk encryption keys for a host behaves differently depending on the OS.

After the key is retrieved, it's stored in the host_disk_encryption_keys table. The value for the key is encrypted using Fleet's CA certificate, and thus can only be decrypted if you have the CA private key.

FileVault (macOS)

For macOS, disk encryption involves a two step process:

  1. Sending a profile with two payloads:

    1. A Payload to configure how the disk is going to be encrypted
    2. A Payload to configure the escrow of the encryption key
  2. Retrieving the disk encryption key:

    1. Via osquery, we grab the (encrypted) disk encryption key
    2. In a cron job, we verify that we're able to decrypt the key. It's necessary to verify if a key is encrypted because we could have grabbed a key generated by a third-party MDM, or an invalid key.

If we're not able to decrypt the key for a host, the key needs to be rotated. Rotation happens silently by:

  1. The server sends a notification to orbit, notifying that the key couldn't be decrypted.
  2. orbit installs an authorization plugin named Escrow Buddy that performs the key rotation the next time the user logs in.
  3. Fleet retrieves and tries to validate the key again.

BitLocker (Windows)

Disk encryption in Windows is performed entirely by orbit.

When disk encryption is enabled, the server sends a notification to orbit, who calls the Win32_EncryptableVolume class to encrypt/decrypt the disk and generate an encryption key.

After the disk is encrypted, orbit sends the key back to the server using an orbit-authenticated endpoint (POST /api/fleet/orbit/disk_encryption_key)

Load testing

osquery-perf supports MDM load testing for Windows and Apple devices. Under the hood it uses the mdmtest package to simulate MDM clients.

Documentation about setting up load testing for MDM can be found in the infrastructure loadtesting README

ADE

For a high-level overview of how ADE works please check the glossary

Below is a summary of Fleet-specific behaviors for ADE.

Sync

Sincronization of devices from all ABM tokens uploaded to Fleet happen in the dep_syncer cron job, which runs every minute, but can be configured by setting mdm.apple_dep_sync_periodicity to a duration string (e.g. "30s", "5m", "1h") in the config.

We keep a record of all devices ingested via the ADE sync in the host_dep_assignments table. Entries in this table are soft-deleted.

On every run, we pull the list of added/modified/deleted devices and:

  1. If the host was added/modified, we:
    1. Create/match a row in the hosts table for the new host. This allows IT admin to move the host between fleets before it turns on MDM or has fleetd installed.
    2. Assign the corresponding JSON profile to each host using ABM's APIs.
  2. If the host was deleted, we soft delete the host_dep_assignments entry

Special case: host in ABM is deleted in Fleet

If an IT admin deletes a host in the UI/API, and we have a non-deleted entry in host_dep_assignments for the host, we immediately create a new host entry as if the device was just ingested from the ABM sync.

End user authentication

macOS setup experience: bootstrap package

Before downloading the bootstrap package using GET /api/latest/fleet/mdm/bootstrap, the Apple device will make one or more HEAD requests to that URL. Fleet server does not support the HEAD request and will return 405 Method Not Allowed.