fleet/docs/Contributing/product-groups/mdm/android-mdm.md
2026-03-10 10:35:58 -05:00

7.7 KiB

Android

Using Android MDM for development

By default, Fleet server uses fleetdm.com proxy to access the Google Android Management API. However, the proxy limits each Fleet server URL to 1 Android enterprise. This means that you should turn off Android MDM before wiping your development DB.

You can also setup a local proxy or use the Google API directly.

Android MDM can't be turned on with localhost server URL (e.g. https://localhost:8412), so your server can receive STATUS_REPORT via PubSub notifications. Use ngrok or a similar solution to expose your local server to the public internet.

Setup a Google project and service account

Instead of creating your own, you can use the shared Android DEV Google cloud project. The credentials for the Android DEV google cloud project can be found in 1Password.

Create a Google service account with the following Roles

  • Android Management User
  • Pub/Sub Admin

To do so:

Run an Android MDM environment with a local proxy

If you're hosting your own Fleet website instance, you need to set the following in website/config/custom.js:

  // Android proxy
  androidEnterpriseProjectId: 'yourProject-123456',
  androidEnterpriseServiceAccountEmailAddress: 'yourUser@yourProject-123456.iam.gserviceaccount.com',
  androidEnterpriseServiceAccountPrivateKey: '-----BEGIN PRIVATE KEY-----\nDATA\n-----END PRIVATE KEY-----\n',

And also set the following env var for your Fleet server:

export FLEET_DEV_ANDROID_PROXY_ENDPOINT=https://public.url.of.your.website/api/endpoint/

Then, launch the website and your Fleet server.

Run an Android MDM environment connected directly to Google

Set the following env vars:

export FLEET_DEV_ANDROID_GOOGLE_CLIENT=1
export FLEET_DEV_ANDROID_GOOGLE_SERVICE_CREDENTIALS=$(cat credentials.json)

To turn on Android MDM, use a Chrome private window (so that you are not logged in with your "fleetdm.com" address). This is only required to enable Android MDM, you can use a normal window for the rest. In "Settings -> Integrations -> MDM -> Turn On Android -> Connect", use a personal email address (not a "fleetdm.com" one). Select "Sign-up for Android only". Domain name is not important ("test.com" for example). No need to fill anything in the "Data protection officer" and "EU representative" sections, just check the checkbox.

If it fails enabling Android MDM due to an already existing enterprise (error "This enterprise is already enrolled with another EMM." when attempting to enable it again) and a personal (gmail) account was used, you must go to https://play.google.com/work, click "Admin settings", and delete the organization that was created the last time (e.g. "test.com"). You will then be able to enable Android MDM again.

There's also a command-line tool in tools/android that can list/delete/etc. enterprises associated with the service account.

Known issues and limitations

  • The Fleet server URL must be public for pub/sub to work properly.
  • The Fleet server URL cannot change -- pub/sub is set up with one URL. See issue Allow Fleet server URL update when using Android
  • Network reliability issues may leave the Android enterprise flow in a broken state. For example, if fleetdm.com proxy creates an Android enterprise but Fleet server goes offline and does not receive the secret key.

Architecture diagrams

---
title: Enable Android MDM
---
sequenceDiagram
    autonumber
    actor Admin
    participant Fleet server
    participant fleetdm.com
    participant Google

    Admin->>+Fleet server: Enable Android
    Fleet server->>+fleetdm.com: Get signup url
    fleetdm.com->>+Google: Get signup url
    Google-->>-fleetdm.com: Signup url
    fleetdm.com-->>-Fleet server: Signup url
    Fleet server->>-Admin: UI redirect (new page)

    Admin->>Google: Enterprise signup
    activate Google
    Google->>Fleet server: Signup callback
    deactivate Google
    activate Fleet server
    Fleet server->>+fleetdm.com: Create enterprise/policy/pubsub
    fleetdm.com->>+Google: Create enterprise/policy/pubsub
    Google-->>-fleetdm.com: Created
    fleetdm.com-->>-Fleet server: Created
    Fleet server->>Admin: Self-closing HTML page
    Fleet server--)Admin: Android enabled (SSE)
    deactivate Fleet server
---
title: Enroll BYOD Android device
---
sequenceDiagram
    autonumber
    actor Admin
    actor Employee
    participant Enroll page
    participant Fleet server
    participant fleetdm.com
    participant Google

    Admin->>+Fleet server: Get signup link
    Fleet server-->>-Admin: Signup link

    Admin->>Employee: Email signup link
    Employee->>+Fleet server: Click signup link
    Fleet server-->>-Enroll page: HTML page
    Employee->>+Enroll page: Click enroll
    Enroll page->>+Fleet server: Get enroll token
    Fleet server->>+fleetdm.com: Get enroll token
    fleetdm.com->>+Google: Get enroll token
    Google-->>-fleetdm.com: Enroll token
    fleetdm.com-->>-Fleet server: Enroll token
    Fleet server-->>-Enroll page: Enroll token
    Enroll page->>-Employee: Redirect to enroll flow

    Employee->>+Google: Enroll device
    Google-->>Employee: Device enrolled
    Google--)Fleet server: Pub/Sub push: ENROLLMENT
    Google--)-Fleet server: Pub/Sub push: STATUS_REPORT

    Admin->>+Fleet server: Get hosts
    Fleet server-->>-Admin: Hosts (including Android)
---
title: Partial class diagram
config:
  class:
    hideEmptyMembersBox: true
---
classDiagram
    direction LR
    class `android.Service`
    <<interface>> `android.Service`
    class `android/service.Service`
    `android/service.Service` ..|> `android.Service`: implements

    class `fleet.AndroidDatastore`
    <<interface>> `fleet.AndroidDatastore`
    class `fleet.Datastore`
    <<interface>> `fleet.Datastore`
    class `android.Datastore`
    <<interface>> `android.Datastore`
    `android/service.Service` *-- `fleet.AndroidDatastore`: uses
    `fleet.Datastore` *-- `fleet.AndroidDatastore`: contains
    `mysql.Datastore` ..|> `fleet.Datastore`: implements
    `fleet.AndroidDatastore` *-- `android.Datastore`: contains
    `mysql.Datastore` *-- `android.Datastore`: contains
    `android/mysql.Datastore` ..|> `android.Datastore`: implements

Security and authentication

Android enterprise signup callback is authenticated by a token in the callback URL. The token is created by Fleet server.

Getting the Android device enrollment token is authenticated with the Fleet enroll secret.

Pub/sub push callback is authenticated by a token query parameter. This token is created by Fleet server. As of June 2025, this token cannot be easily rotated. We could add another level of authentication where the Fleet server would need to check with Google to authenticate the pub/sub message: