MDM

Zentral can be used as MDM server for Apple devices.

Zentral configuration

To activate the MDM module, you need to add a zentral.contrib.mdm section to the apps section in base.json.

SCEP CA issuer chain

To authenticate the OTA enrollments, Zentral needs the SCEP CA issuer certificate chain in PEM form in the scep_ca_fullchain key of the zentral.contrib.mdm section. It is possible to use the {{ file:PATH_TO_PEM_CHAIN }} substitution to load the chain from a file on disk.

mTLS proxy

Zentral is expecting the client certificate in PEM form in the X-SSL-Client-Cert header, and the client certificate subject DN in the X-SSL-Client-S-DN header. If this is not possible, you can set mtls_proxy to false in the zentral.contrib.mdm section. In that case, the Apple devices will be configured to add a header containing the payload signature in each HTTP request. See the Apple documentation. This adds approximately 2KB of data to each message.

Push certificates

To be able to send notifications to the devices, Zentral needs a push certificate (aka. APNS certificate). To get one, you first need to generate an MDM vendor certificate. An Apple Developer Enterprise Account with the ability to generate MDM CSRs is required. You can then use this vendor certificate to sign an APNS certificate request. The mdmcerts Zentral management command can be used to help with this process.

MDM vendor certificate

Run the following command to setup a working directory with a vendor certificate request:

python server/manage.py mdmcerts -d the_working_directory init
  • Choose a password for the vendor certificate request private key, and remember it!

The content of the working directory should be the following:

$ ls the_working_directory
vendor.csr  vendor.key

Push/APNS certificate

Run the following command to create an APNS certificate request and sign it with the vendor certificate:

python server/manage.py mdmcerts -d the_working_directory req COUNTRYCODE
  • Choose a password for the push/APNS certificate request private key, and remember it!
  • Enter the password for the MDM vendor certificate private key.

The content of the working directory should be the following:

$ ls the_working_directory
push.b64  push.csr  push.key  vendor.crt  vendor.csr  vendor.key
  • Sign in to the Apple Push Certificate Portal.
  • To renew an existing certificate, choose the certificate and click the Renew button.
  • To create a new certificate, click the Create a Certificate button.
  • Upload the push.b64 signed certificate request.
  • Download the generated certificate.

Navigate to the Zentral MDM > Push certificates section, and either select an existing certificate and click on the Update button to renew an existing certificate, or click on the Add button to create a new push certificate. Upload the generated certificate, the push.key private key, and enter the password of the push certificate private key.

Renewing a Push/APNS certificate

IMPORTANT do not let the push/APNS certificates expire! Remember to renew them ahead of their expiry!

To be able to keep sending notifications to enrolled devices, it is important to renew the existing certificates, and not generate new ones (it it important that the topic of a push certificate stays the same). In the Apple Push Certificate Portal, look for the existing certificate and click on the Renew button, and not on the Create a Certificate button. In the Zentral MDM > Push certificates section, find the certificate and click on the Update button, and do not Add a new certificate.

HTTP API

/api/mdm/dep/virtual_servers/<int:pk>/sync_devices/

  • method: POST
  • required permission: mdm.view_depvirtualserver

Use this endpoint to trigger a DEP virtual server devices sync.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/dep/virtual_servers/1/sync_devices/ \
  | python3 -m json.tool

Response:

{
  "task_id": "b1512b8d-1e17-4181-a1c3-93a7243fddd4",
  "task_result_url": "/api/task_result/b1512b8d-1e17-4181-a1c3-93a7243fddd4/"
}

/api/mdm/devices/

  • method: GET
  • required permission: mdm.view_enrolleddevice
  • available filters:
    • serial_number
    • udid

Use this endpoint to list the MDM enrolled devices.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/devices/?serial_number=012345678910

Response:

[
  {
    "id": 27,
    "udid": "2A7F9BCE-9B52-4073-BE21-E419C85068E9",
    "serial_number": "012345678910",
    "name": "John’s Mac mini",
    "model": "Macmini8,1",
    "platform": "macOS",
    "os_version": "14.2.1",
    "build_version": "23C71",
    "apple_silicon": false,
    "cert_not_valid_after": "2024-08-05T14:44:01",
    "blueprint": 1,
    "awaiting_configuration": false,
    "declarative_management": true,
    "dep_enrollment": true,
    "user_enrollment": false,
    "user_approved_enrollment": true,
    "supervised": true,
    "bootstrap_token_escrowed": true,
    "filevault_enabled": true,
    "filevault_prk_escrowed": true,
    "activation_lock_manageable": true,
    "last_seen_at": "2024-02-17T20:31:34.848107",
    "last_notified_at": "2024-03-27T20:44:21.751091",
    "checkout_at": null,
    "blocked_at": null,
    "created_at": "2023-08-06T14:44:01.847058",
    "updated_at": "2024-02-17T20:31:34.848262"
  }
]

/api/mdm/devices/<int:pk>/block/

  • method: POST
  • required permission: mdm.change_enrolleddevice

Blocks an enrolled device. Releases it from the MDM and denies further MDM enrollments. A serialized device is returned.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/devices/27/block/

Response:

{
  "id": 27,
  "udid": "2A7F9BCE-9B52-4073-BE21-E419C85068E9",
  "serial_number": "012345678910",
  "name": "John’s Mac mini",
  "model": "Macmini8,1",
  "platform": "macOS",
  "os_version": "14.2.1",
  "build_version": "23C71",
  "apple_silicon": false,
  "cert_not_valid_after": "2024-08-05T14:44:01",
  "blueprint": 1,
  "awaiting_configuration": false,
  "declarative_management": true,
  "dep_enrollment": true,
  "user_enrollment": false,
  "user_approved_enrollment": true,
  "supervised": true,
  "bootstrap_token_escrowed": true,
  "filevault_enabled": true,
  "filevault_prk_escrowed": true,
  "activation_lock_manageable": true,
  "last_seen_at": "2024-02-17T20:31:34.848107",
  "last_notified_at": "2024-03-27T20:44:21.751091",
  "checkout_at": null,
  "blocked_at": "2024-02-04T07:03:12.345678",
  "created_at": "2023-08-06T14:44:01.847058",
  "updated_at": "2024-02-04T07:03:12.456789",
}

/api/mdm/devices/<int:pk>/unblock/

  • method: POST
  • required permission: mdm.change_enrolleddevice

Unblocks an enrolled device. The device can enroll again. A serialized device is returned.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/devices/27/unblock/

Response:

{
  "id": 27,
  "udid": "2A7F9BCE-9B52-4073-BE21-E419C85068E9",
  "serial_number": "012345678910",
  "name": "John’s Mac mini",
  "model": "Macmini8,1",
  "platform": "macOS",
  "os_version": "14.2.1",
  "build_version": "23C71",
  "apple_silicon": false,
  "cert_not_valid_after": "2024-08-05T14:44:01",
  "blueprint": 1,
  "awaiting_configuration": false,
  "declarative_management": true,
  "dep_enrollment": true,
  "user_enrollment": false,
  "user_approved_enrollment": true,
  "supervised": true,
  "bootstrap_token_escrowed": true,
  "filevault_enabled": true,
  "filevault_prk_escrowed": true,
  "activation_lock_manageable": true,
  "last_seen_at": "2024-02-17T20:31:34.848107",
  "last_notified_at": "2024-03-27T20:44:21.751091",
  "checkout_at": null,
  "blocked_at": null,
  "created_at": "2023-08-06T14:44:01.847058",
  "updated_at": "2024-02-17T20:31:34.848262"
}

/api/mdm/devices/<int:pk>/erase/

  • method: POST
  • required permission: mdm.add_devicecommand
  • arguments:
    • disallow_proximity_setup
    • preserve_data_plan
    • pin

Queues up an EraseDevice command for the device and notifies it.

On an Apple Silicon device, no arguments are required. For a T1 machine, the pin argument is required. For a mobile device, no pin can be set, but disallow_proximity_setup and preserve_data_plan are required.

A serialized device command is returned.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
  https://$ZTL_FQDN/api/mdm/devices/27/erase/

Response:

{
  "id": 815,
  "uuid": "4ec709ba-542e-4adf-8002-7d782e9eae9e",
  "enrolled_device": 27,
  "name": "EraseDevice",
  "artifact_version": null,
  "artifact_operation": null,
  "not_before": null,
  "time": null,
  "result": null,
  "result_time": null,
  "status": null,
  "error_chain": null,
  "created_at": "2024-03-28T16:27:05.829954",
  "updated_at": "2024-03-28T16:27:05.829959"
}

/api/mdm/devices/<int:pk>/filevault_prk/

  • method: GET
  • required permission: mdm.view_filevault_prk

Returns the decrypted FileVault PRK for an MDM enrolled device.

Example:

curl -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/devices/27/filevault_prk/

Response:

{
  "id": 27,
  "serial_number": "012345678910",
  "filevault_prk": "0000-0000-0000-0000-0000-0000"
}

/api/mdm/devices/<int:pk>/lock/

  • method: POST
  • required permission: mdm.add_devicecommand
  • arguments:
    • message
    • phone_number
    • pin

Queues up a DeviceLock command for the device and notifies it.

pin can only be set, and is required for macOS devices. message and phone_number are optional.

A serialized device command is returned.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pin": "012345", "message": "This device is locked!", "phone_number": "+49000000000"}'
  https://$ZTL_FQDN/api/mdm/devices/27/lock/

Response:

{
  "id": 815,
  "uuid": "4ec709ba-542e-4adf-8002-7d782e9eae9e",
  "enrolled_device": 27,
  "name": "DeviceLock",
  "artifact_version": null,
  "artifact_operation": null,
  "not_before": null,
  "time": null,
  "result": null,
  "result_time": null,
  "status": null,
  "error_chain": null,
  "created_at": "2024-03-28T16:27:05.829954",
  "updated_at": "2024-03-28T16:27:05.829959"
}

/api/mdm/devices/<int:pk>/recovery_password/

  • method: GET
  • required permission: mdm.view_recovery_password

Returns the decrypted recovery lock or firmware password for an MDM enrolled device.

Example:

curl -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/devices/27/recovery_password/

Response:

{
  "id": 27,
  "serial_number": "012345678910",
  "recovery_password": "000000000000"
}

/api/mdm/locations/

  • method: GET
  • required permission: mdm.view_location
  • available filters:
    • name
    • organization_name

Fetches the list of Apps / Books locations.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/locations/

Result:

[
  {
    "id": 388,
    "server_token_expiration_date": "2050-01-01T00:00:00",
    "organization_name": "Organization name",
    "name": "Location name",
    "country_code": "DE",
    "library_uid": "1dc05825-af1d-422a-9b26-72a2f8c2aae5",
    "platform": "enterprisestore",
    "website_url": "https://business.apple.com",
    "mdm_info_id": "f42d9d70-d304-4ac1-83db-b045fa4bc623",
    "created_at": "2024-03-28T17:58:15.948083",
    "updated_at": "2024-03-28T17:58:15.948088"
  }
]

/api/mdm/location_assets/

  • method: GET
  • required permission: mdm.view_locationasset
  • available filters:
    • adam_id
    • pricing_param
    • location_id

Fetches the list of Apps / Books location assets.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/location_assets/?location_id=749

Result:

[
  {
    "id": 414,
    "assigned_count": 0,
    "available_count": 0,
    "retired_count": 0,
    "total_count": 0,
    "created_at": "2024-03-28T19:15:58.212233",
    "updated_at": "2024-03-28T19:15:58.212237",
    "location": 749,
    "asset": 489
  }
]

/api/mdm/software_updates/sync/

  • method: POST
  • required permission:
    • mdm.add_softwareupdate
    • mdm.change_softwareupdate
    • mdm.delete_softwareupdate

Use this endpoint to trigger a Software Updates sync.

Example:

curl -XPOST \
  -H "Authorization: Token $ZTL_API_TOKEN" \
  https://$ZTL_FQDN/api/mdm/software_updates/sync/ \
  | python3 -m json.tool

Response:

{
  "task_id": "b1512b8d-1e17-4181-a1c3-93a7243fddd4",
  "task_result_url": "/api/task_result/b1512b8d-1e17-4181-a1c3-93a7243fddd4/"
}