[PR #6435] [client] MDM Android mobile wiring #28212

Open
opened 2026-08-05 08:05:59 -04:00 by saavagebueno · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/netbirdio/netbird/pull/6435
Author: @riccardomanfrin
Created: 6/15/2026
Status: 🔄 Open

Base: mainHead: mdm_integration


📝 Commits (9)

  • db2c9b6 MDM Android mobile wiring
  • bec26d5 Removes dead code
  • 8741954 Removes static vars
  • 0340893 Now we need to apply MDM in the GetConfig
  • b2c5732 You now need to explicitly call these around
  • 7715c38 Adds iOS wiring
  • e1ffb16 Merge branch 'main' into mdm_integration (with unresolved conflict markers)
  • e92aa7d Resolve merge conflicts from main
  • f91f9fc Convey MDM overlay config to Debug Bundle output

📊 Changes

20 files changed (+507 additions, -171 deletions)

View changed files

📝 client/android/client.go (+11 -0)
client/android/mdm.go (+80 -0)
📝 client/cmd/login.go (+6 -0)
📝 client/cmd/up.go (+5 -0)
📝 client/embed/embed.go (+5 -0)
📝 client/internal/profilemanager/config.go (+23 -12)
📝 client/internal/profilemanager/config_mdm_test.go (+66 -51)
📝 client/ios/NetBirdSDK/client.go (+12 -0)
client/ios/NetBirdSDK/mdm.go (+82 -0)
📝 client/mdm/policy.go (+37 -5)
📝 client/mdm/policy_darwin.go (+11 -3)
📝 client/mdm/policy_mobile.go (+10 -9)
📝 client/mdm/policy_other.go (+12 -8)
📝 client/mdm/policy_test.go (+6 -4)
📝 client/mdm/policy_windows.go (+11 -3)
📝 client/mdm/ticker.go (+16 -16)
📝 client/mdm/ticker_test.go (+38 -29)
📝 client/server/mdm.go (+0 -4)
📝 client/server/server.go (+32 -3)
📝 client/server/setconfig_mdm_test.go (+44 -24)

📄 Description

Describe your changes

Adds the Android-side of the MDM bridge that lets the mobile gomobile
binding accept a managed-configuration snapshot from the native layer
(Kotlin) and surface it through the same Config.apply → applyMDMPolicy → loadMDMPolicy → loadPlatformPolicy chain the desktop
loaders already use.

Two files:

  • client/mdm/policy_mobile.go (replaces the previous (nil, nil)
    stub). Defines an internal PolicyFetcher interface and a
    SetMobilePolicyFetcher setter; the registered fetcher is what
    loadPlatformPolicy calls on every read. Set-once at gomobile
    init, never mutated at runtime → no synchronisation required for
    the read path.

  • client/android/mdm.go (new). The gomobile-facing bridge.
    Exposes a Java/Kotlin-friendly PolicyFetcher interface with a
    single FetchJSON() string method — map[string]any cannot
    cross the gomobile JNI boundary, so the native layer returns a
    JSON-encoded snapshot and a Go-side jsonFetcherAdapter parses it
    back into the internal mdm.PolicyFetcher. Adds
    Client.OnMDMPolicyChanged() as the entry point the native
    broadcast receiver invokes when the OS reports a managed-config
    change; the method cancels the current engine context so the
    outer native loop relaunches Run() and the new run picks up
    the fresh policy.

Runtime flow on a managed-config change (mobile):

  1. OS broadcasts ACTION_APPLICATION_RESTRICTIONS_CHANGED (Android)
    / UserDefaults.didChangeNotification (iOS).
  2. Native bridge calls Client.OnMDMPolicyChanged()Stop()
    engine teardown.
  3. Native loop relaunches Run().
  4. UpdateOrCreateConfigConfig.apply
    applyMDMPolicy(loadMDMPolicy())
    LoadPolicy → loadPlatformPolicy
    fetcher.Fetch() → adapter FetchJSON() callback into Kotlin
    RestrictionsManager.getApplicationRestrictions() parsed and
    returned to Go.
  5. Engine restarts with the fresh MDM overlay.

No ticker on mobile (the desktop ticker stays unchanged in
client/server, build-tagged !ios && !android at the call site
via service_controller.go). The OS notification is authoritative;
no Go-side diff loop, no cached policy snapshot in either Go or
Kotlin — RestrictionsManager is the single source of truth.

The Kotlin / Java side of the wiring (the BroadcastReceiver, the
fetcher implementation, the app_restrictions.xml schema, the
Settings UI lock-on-managed-keys, the EngineRestarter wiring) lives
in the netbirdio/android-client repo and is shipped in a separate
PR there.

e2e testing done on Android.

No public issue. Internal task to land the Android half of the MDM
managed-configuration feature originally shipped for desktop in PR
https://github.com/netbirdio/netbird/pull/6374. The mobile half was
called out in that PR's description as a follow-up. This PR is the
Go-side of that follow-up; the Kotlin side is a companion PR against
netbirdio/android-client.

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

The desktop MDM documentation in netbirdio/docs already describes
the managed-key set and the per-platform mappings (Windows registry,
macOS plist). The mobile path consumes the exact same key set, so no
new key documentation is needed. The Kotlin / Java integration of
the bridge — MDMPolicyFetcher, the BroadcastReceiver, the
app_restrictions.xml manifest entry — is shipped and documented in
the companion netbirdio/android-client PR; the Go-side public
surface added here (Client.OnMDMPolicyChanged,
SetMobilePolicyFetcher, PolicyFetcher) is implementation glue
that only the in-repo native bridge calls, not an API end users
configure directly.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

N/A

Summary by CodeRabbit

Release Notes

  • New Features

    • Android can now ingest managed device policy updates from the mobile platform’s managed configuration data.
    • MDM policy loading is now dependency-injected, with daemon and config generation consistently applying the active MDM overlay (including periodic refresh).
  • Bug Fixes

    • Correct handling of “no policy source” vs an empty policy.
    • Malformed managed policy JSON is treated as empty (with a warning).
    • MDM overlays are applied safely to avoid stale or nil policy results.
  • Tests

    • Updated MDM tests to use per-instance loader-based injection instead of global overrides.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/netbirdio/netbird/pull/6435 **Author:** [@riccardomanfrin](https://github.com/riccardomanfrin) **Created:** 6/15/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `mdm_integration` --- ### 📝 Commits (9) - [`db2c9b6`](https://github.com/netbirdio/netbird/commit/db2c9b6f49ac88343f546c14476c47e2a3fe7268) MDM Android mobile wiring - [`bec26d5`](https://github.com/netbirdio/netbird/commit/bec26d5a14e7ac6f1d97ff533bf5275577bb30db) Removes dead code - [`8741954`](https://github.com/netbirdio/netbird/commit/874195440cf0f011355ee88dce4cdbe2649a4e87) Removes static vars - [`0340893`](https://github.com/netbirdio/netbird/commit/034089385468346d1a3765b99d97c9ea314388ca) Now we need to apply MDM in the GetConfig - [`b2c5732`](https://github.com/netbirdio/netbird/commit/b2c5732847ae5c22ab8f526204d9f121df72869e) You now need to explicitly call these around - [`7715c38`](https://github.com/netbirdio/netbird/commit/7715c382ee37aa7f541a1b3eaa4d41d18dc95959) Adds iOS wiring - [`e1ffb16`](https://github.com/netbirdio/netbird/commit/e1ffb165a4e4509363133604de8587dc44710030) Merge branch 'main' into mdm_integration (with unresolved conflict markers) - [`e92aa7d`](https://github.com/netbirdio/netbird/commit/e92aa7dfb06858dc6e455d77fe0fc34d697f3e91) Resolve merge conflicts from main - [`f91f9fc`](https://github.com/netbirdio/netbird/commit/f91f9fc05c04c5dac66b0250777a116272990d60) Convey MDM overlay config to Debug Bundle output ### 📊 Changes **20 files changed** (+507 additions, -171 deletions) <details> <summary>View changed files</summary> 📝 `client/android/client.go` (+11 -0) ➕ `client/android/mdm.go` (+80 -0) 📝 `client/cmd/login.go` (+6 -0) 📝 `client/cmd/up.go` (+5 -0) 📝 `client/embed/embed.go` (+5 -0) 📝 `client/internal/profilemanager/config.go` (+23 -12) 📝 `client/internal/profilemanager/config_mdm_test.go` (+66 -51) 📝 `client/ios/NetBirdSDK/client.go` (+12 -0) ➕ `client/ios/NetBirdSDK/mdm.go` (+82 -0) 📝 `client/mdm/policy.go` (+37 -5) 📝 `client/mdm/policy_darwin.go` (+11 -3) 📝 `client/mdm/policy_mobile.go` (+10 -9) 📝 `client/mdm/policy_other.go` (+12 -8) 📝 `client/mdm/policy_test.go` (+6 -4) 📝 `client/mdm/policy_windows.go` (+11 -3) 📝 `client/mdm/ticker.go` (+16 -16) 📝 `client/mdm/ticker_test.go` (+38 -29) 📝 `client/server/mdm.go` (+0 -4) 📝 `client/server/server.go` (+32 -3) 📝 `client/server/setconfig_mdm_test.go` (+44 -24) </details> ### 📄 Description ## Describe your changes Adds the Android-side of the MDM bridge that lets the mobile gomobile binding accept a managed-configuration snapshot from the native layer (Kotlin) and surface it through the same `Config.apply → applyMDMPolicy → loadMDMPolicy → loadPlatformPolicy` chain the desktop loaders already use. Two files: - `client/mdm/policy_mobile.go` (replaces the previous `(nil, nil)` stub). Defines an internal `PolicyFetcher` interface and a `SetMobilePolicyFetcher` setter; the registered fetcher is what `loadPlatformPolicy` calls on every read. Set-once at gomobile init, never mutated at runtime → no synchronisation required for the read path. - `client/android/mdm.go` (new). The gomobile-facing bridge. Exposes a Java/Kotlin-friendly `PolicyFetcher` interface with a single `FetchJSON() string` method — `map[string]any` cannot cross the gomobile JNI boundary, so the native layer returns a JSON-encoded snapshot and a Go-side `jsonFetcherAdapter` parses it back into the internal `mdm.PolicyFetcher`. Adds `Client.OnMDMPolicyChanged()` as the entry point the native broadcast receiver invokes when the OS reports a managed-config change; the method cancels the current engine context so the outer native loop relaunches `Run()` and the new run picks up the fresh policy. Runtime flow on a managed-config change (mobile): 1. OS broadcasts `ACTION_APPLICATION_RESTRICTIONS_CHANGED` (Android) / `UserDefaults.didChangeNotification` (iOS). 2. Native bridge calls `Client.OnMDMPolicyChanged()` → `Stop()` → engine teardown. 3. Native loop relaunches `Run()`. 4. `UpdateOrCreateConfig` → `Config.apply` → `applyMDMPolicy(loadMDMPolicy())` → `LoadPolicy → loadPlatformPolicy` → `fetcher.Fetch()` → adapter `FetchJSON()` callback into Kotlin → `RestrictionsManager.getApplicationRestrictions()` parsed and returned to Go. 5. Engine restarts with the fresh MDM overlay. No ticker on mobile (the desktop ticker stays unchanged in `client/server`, build-tagged `!ios && !android` at the call site via `service_controller.go`). The OS notification is authoritative; no Go-side diff loop, no cached policy snapshot in either Go or Kotlin — `RestrictionsManager` is the single source of truth. The Kotlin / Java side of the wiring (the BroadcastReceiver, the fetcher implementation, the `app_restrictions.xml` schema, the Settings UI lock-on-managed-keys, the EngineRestarter wiring) lives in the `netbirdio/android-client` repo and is shipped in a separate PR there. e2e testing done on Android. ## Issue ticket number and link No public issue. Internal task to land the Android half of the MDM managed-configuration feature originally shipped for desktop in PR https://github.com/netbirdio/netbird/pull/6374. The mobile half was called out in that PR's description as a follow-up. This PR is the Go-side of that follow-up; the Kotlin side is a companion PR against `netbirdio/android-client`. ## Stack <!-- branch-stack --> ### Checklist - [ ] Is it a bug fix - [ ] Is a typo/documentation fix - [x] Is a feature enhancement - [ ] It is a refactor - [ ] Created tests that fail without the change (if possible) > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) The desktop MDM documentation in `netbirdio/docs` already describes the managed-key set and the per-platform mappings (Windows registry, macOS plist). The mobile path consumes the exact same key set, so no new key documentation is needed. The Kotlin / Java integration of the bridge — `MDMPolicyFetcher`, the BroadcastReceiver, the `app_restrictions.xml` manifest entry — is shipped and documented in the companion `netbirdio/android-client` PR; the Go-side public surface added here (`Client.OnMDMPolicyChanged`, `SetMobilePolicyFetcher`, `PolicyFetcher`) is implementation glue that only the in-repo native bridge calls, not an API end users configure directly. ### Docs PR URL (required if "docs added" is checked) Paste the PR link from https://github.com/netbirdio/docs here: N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Android can now ingest managed device policy updates from the mobile platform’s managed configuration data. * MDM policy loading is now dependency-injected, with daemon and config generation consistently applying the active MDM overlay (including periodic refresh). * **Bug Fixes** * Correct handling of “no policy source” vs an empty policy. * Malformed managed policy JSON is treated as empty (with a warning). * MDM overlays are applied safely to avoid stale or nil policy results. * **Tests** * Updated MDM tests to use per-instance loader-based injection instead of global overrides. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
saavagebueno added the pull-request label 2026-08-05 08:05:59 -04:00
Sign in to join this conversation.
No Label pull-request
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#28212