Skip to content

feat: add MDM managed app configuration support#170

Open
dbrieck wants to merge 3 commits intonetbirdio:mainfrom
dbrieck:feat/mdm-managed-config
Open

feat: add MDM managed app configuration support#170
dbrieck wants to merge 3 commits intonetbirdio:mainfrom
dbrieck:feat/mdm-managed-config

Conversation

@dbrieck
Copy link
Copy Markdown

@dbrieck dbrieck commented Apr 24, 2026

Summary

Add Android Enterprise managed configurations support, allowing MDM solutions (Intune, Google Workspace, etc.) to push NetBird configuration to managed devices for zero-touch deployment.

Related to netbirdio/netbird#1918

Changes

  • app/src/main/res/xml/app_restrictions.xml — Android Enterprise managed configuration schema defining 7 keys: managementUrl, setupKey, adminUrl, preSharedKey, rosenpassEnabled, rosenpassPermissive, disableAutoConnect
  • app/src/main/AndroidManifest.xml — Registered APP_RESTRICTIONS metadata and ManagedConfigReceiver broadcast receiver
  • tool/.../ManagedConfigReader.java — Reads MDM config from RestrictionsManager.getApplicationRestrictions() into Go SDK ManagedConfig
  • tool/.../ManagedConfigReceiver.javaBroadcastReceiver for ACTION_APPLICATION_RESTRICTIONS_CHANGED to handle runtime MDM updates
  • tool/.../EngineRunner.java — Applies MDM config and auto-login with setup key before VPN engine start

How It Works

  1. MDM pushes managed configuration via Android Enterprise
  2. On VPN service start, ManagedConfigReader reads from RestrictionsManager
  3. Config is applied to NetBird client configuration (overrides user settings)
  4. If a setup key is provided, automatic enrollment is attempted
  5. Runtime config changes are handled via ManagedConfigReceiver

Dependencies

Summary by CodeRabbit

  • New Features
    • Added Mobile Device Management (MDM) support for managed app configuration and seven configurable keys (management/admin URLs, setup key, pre-shared key, Rosenpass toggles, disable-auto-connect).
    • App can automatically apply updated managed settings when they change.
    • Silent setup-key registration attempt during startup if a setup key is provided.
    • New user-facing labels and descriptions for all MDM options.

Integrate Android Enterprise managed configurations to allow MDM
solutions (Intune, Google Workspace, etc.) to push NetBird configuration
to managed devices.

- Add app_restrictions.xml schema defining 7 MDM configuration keys
- Add ManagedConfigReader to read RestrictionsManager values
- Add ManagedConfigReceiver for runtime MDM config change events
- Register restrictions metadata and receiver in AndroidManifest.xml
- Apply MDM config and auto-login with setup key in EngineRunner

Related to netbirdio/netbird#1918
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@dbrieck has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 17 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 17 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 32d1e0a6-0ff9-49d4-8399-0918b365148d

📥 Commits

Reviewing files that changed from the base of the PR and between 6ec5073 and 4a5ba6b.

📒 Files selected for processing (1)
  • tool/src/main/java/io/netbird/client/tool/EngineRunner.java
📝 Walkthrough

Walkthrough

Android Enterprise Managed Configuration support added: new app_restrictions XML, ManagedConfigReader, and ManagedConfigReceiver. EngineRunner now attempts to apply MDM config (and optionally silent setup-key login) during startup; receiver applies changes on ACTION_APPLICATION_RESTRICTIONS_CHANGED broadcasts.

Changes

Cohort / File(s) Summary
Manifest & Integration
app/src/main/AndroidManifest.xml
Adds metadata reference to @xml/app_restrictions and declares exported ManagedConfigReceiver for APPLICATION_RESTRICTIONS_CHANGED.
App Restrictions Schema
app/src/main/res/xml/app_restrictions.xml
New XML resource defining managed-config keys: managementUrl, adminUrl, setupKey, preSharedKey, rosenpassEnabled, rosenpassPermissive, disableAutoConnect with defaults and descriptions.
String Resources
app/src/main/res/values/strings.xml
Adds localized titles/descriptions for the new MDM-managed fields.
MDM Reader
tool/src/main/java/io/netbird/client/tool/ManagedConfigReader.java
New class exposing read(Context) and hasManagedConfig(Context) to fetch and parse RestrictionsManager application restrictions into a ManagedConfig object (with safe checks and selective logging).
Receiver
tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java
New BroadcastReceiver handling ACTION_APPLICATION_RESTRICTIONS_CHANGED; uses goAsync(), runs apply logic on executor, synchronizes with EngineRunner.MDM_CONFIG_LOCK, reads config and applies via ProfileManagerWrapper.
Engine Startup
tool/src/main/java/io/netbird/client/tool/EngineRunner.java
Adds MDM_CONFIG_LOCK and updates runClient() to load/apply MDM config before notifying listeners/starting engine; performs synchronous silent setup-key login when present and downgrades login errors to warnings.

Sequence Diagram(s)

sequenceDiagram
    participant System as System
    participant Receiver as ManagedConfigReceiver
    participant Reader as ManagedConfigReader
    participant RM as RestrictionsManager
    participant Engine as EngineRunner
    participant Profile as ProfileManagerWrapper
    participant Config as ManagedConfig

    System->>Receiver: APPLICATION_RESTRICTIONS_CHANGED
    Receiver->>Reader: read(context)
    Reader->>RM: getApplicationRestrictions()
    RM-->>Reader: restrictions bundle
    Reader-->>Receiver: ManagedConfig (or null)
    alt ManagedConfig present
        Receiver->>Engine: synchronize on MDM_CONFIG_LOCK
        Receiver->>Profile: getActiveConfigPath()
        Receiver->>Config: apply(configPath)
        Config-->>Receiver: success / error
    else No config
        Receiver-->>Receiver: log and exit
    end
    note right of Engine: On startup EngineRunner also calls Reader.read() under MDM_CONFIG_LOCK and may perform setup-key login
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐇 I sniffed a new config on the breeze,

Keys and flags tucked in XML trees.
A broadcast hop, a silent login cheer,
The engine wakes — managed settings near.
Hooray, MDM carrots for my ear! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add MDM managed app configuration support' directly and accurately summarizes the main change—adding MDM (Mobile Device Management) managed configuration support to the Android app.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dbrieck dbrieck marked this pull request as ready for review April 24, 2026 13:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
app/src/main/res/xml/app_restrictions.xml (1)

12-59: Consider using string resources for android:title and android:description.

These values are shown to MDM admins in consoles like Intune/Workspace ONE when configuring the app. Hardcoding them here prevents localization and makes it inconsistent with the rest of the app's string handling. Moving them to res/values/strings.xml (and translations) would improve the admin experience in non‑English locales.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/res/xml/app_restrictions.xml` around lines 12 - 59, The XML
currently hardcodes android:title and android:description for the restrictions
(keys: managementUrl, setupKey, adminUrl, preSharedKey, rosenpassEnabled,
rosenpassPermissive, disableAutoConnect); move each title and description into
res/values/strings.xml (add descriptive resource names like
title_management_url, desc_management_url, title_setup_key, desc_setup_key,
etc.), replace the literal text in app_restrictions.xml with the corresponding
`@string/` resource references, and add translations as needed; keep the
android:key and android:defaultValue entries unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java`:
- Around line 107-120: The setup-key login is currently attempted on every
EngineRunner.runClient() because it only checks mdmConfig.hasSetupKey(); change
this to first detect whether the engine/device is already enrolled (e.g., check
the active profile for an existing peer key or a device registration flag) and
only call
io.netbird.gomobile.android.Android.newAuth(...).loginWithSetupKeySync(...) when
not enrolled. In practice modify the block referencing mdmConfig.hasSetupKey()
in EngineRunner to add a precondition like "if mdmConfig.hasSetupKey() &&
!isEnrolled()" (or an equivalent check against the active profile/peer key)
before invoking loginWithSetupKeySync so successful prior enrollments skip the
remote round-trip and log noise.
- Around line 97-124: There is a race between EngineRunner.runClient()'s call to
mdmConfig.apply(configurationFilePath) and ManagedConfigReceiver.onReceive()
mutating the same config; serialize these operations by introducing a shared
lock (e.g., a static final Object MDM_CONFIG_LOCK) used by both
EngineRunner.runClient() and ManagedConfigReceiver.onReceive() around any call
to mdmConfig.apply(configPath), and in runClient() check whether the engine is
already running (or a flag like engineStarted) and skip the apply when the
engine is active to avoid mid-run file writes; update references to
mdmConfig.apply(configurationFilePath), ManagedConfigReceiver.onReceive(), and
the engine start calls (goClient.runSync/goClient.runAsync) to use the
lock/flag.
- Around line 109-114: The MDM setup-key enrollment is calling
Android.newAuth(configurationFilePath, "") which passes an empty server address;
update EngineRunner to use Preferences.defaultServer() instead (i.e., call
Android.newAuth with Preferences.defaultServer()) so behavior matches
ChangeServerFragmentViewModel, or add a brief comment next to the
Android.newAuth call documenting why an empty string is intentional and what the
Go SDK fallback behavior is; locate the call in EngineRunner where
io.netbird.gomobile.android.Auth auth is created and adjust the second argument
accordingly while leaving the subsequent
auth.loginWithSetupKeySync(mdmConfig.getSetupKey(), DeviceName.getDeviceName())
unchanged.

In `@tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java`:
- Around line 26-47: onReceive currently does blocking IPC/JNI/file I/O
(ManagedConfigReader.read, new ProfileManagerWrapper(...),
ProfileManagerWrapper.getActiveConfigPath, ManagedConfig.apply) on the main
thread which risks ANRs; change the receiver to call goAsync() and perform those
calls off the main thread (or submit them to a shared single-threaded EXECUTOR
used by EngineRunner) and ensure you call PendingResult.finish() when done so
work runs asynchronously and no long/blocking operations occur in onReceive.

---

Nitpick comments:
In `@app/src/main/res/xml/app_restrictions.xml`:
- Around line 12-59: The XML currently hardcodes android:title and
android:description for the restrictions (keys: managementUrl, setupKey,
adminUrl, preSharedKey, rosenpassEnabled, rosenpassPermissive,
disableAutoConnect); move each title and description into res/values/strings.xml
(add descriptive resource names like title_management_url, desc_management_url,
title_setup_key, desc_setup_key, etc.), replace the literal text in
app_restrictions.xml with the corresponding `@string/` resource references, and
add translations as needed; keep the android:key and android:defaultValue
entries unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b57820b-55a1-4dc5-bb31-36316eb9346b

📥 Commits

Reviewing files that changed from the base of the PR and between 950ab4c and 045d922.

📒 Files selected for processing (5)
  • app/src/main/AndroidManifest.xml
  • app/src/main/res/xml/app_restrictions.xml
  • tool/src/main/java/io/netbird/client/tool/EngineRunner.java
  • tool/src/main/java/io/netbird/client/tool/ManagedConfigReader.java
  • tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java

Comment on lines +97 to +124
// Apply MDM managed configuration before starting the engine.
// MDM values override user-set preferences on every launch.
try {
io.netbird.gomobile.android.ManagedConfig mdmConfig = ManagedConfigReader.read(context);
if (mdmConfig != null && mdmConfig.hasConfig()) {
mdmConfig.apply(configurationFilePath);
Log.i(LOGTAG, "MDM managed configuration applied");

// If MDM provides a setup key and the engine needs login,
// perform silent registration with the setup key
if (mdmConfig.hasSetupKey()) {
try {
io.netbird.gomobile.android.Auth auth =
io.netbird.gomobile.android.Android.newAuth(configurationFilePath, "");
if (auth != null) {
auth.loginWithSetupKeySync(mdmConfig.getSetupKey(), DeviceName.getDeviceName());
Log.i(LOGTAG, "MDM: silent setup key registration completed");
}
} catch (Exception e) {
// Setup key login may fail if already registered or key expired.
// This is not fatal — continue with normal flow.
Log.w(LOGTAG, "MDM: setup key login skipped or failed: " + e.getMessage());
}
}
}
} catch (Exception e) {
Log.e(LOGTAG, "Failed to apply MDM config, continuing with existing config", e);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Race between runClient() MDM apply and ManagedConfigReceiver.onReceive().

ManagedConfigReceiver.onReceive() (main thread) also calls config.apply(configPath) against the same configuration file used here. There is no synchronization between the two paths, so:

  • If a restriction change is broadcast while runClient() is between mdmConfig.apply(configurationFilePath) (line 102) and goClient.run*(...) (lines 129/131), the config file the Go client ultimately reads may be a partially-updated mix of both writers.
  • On a hot config change mid-session, the receiver mutates the file the running engine is actively using, with no coordination.

Consider serializing MDM application through a shared lock (or a single-threaded queue/handler) used by both EngineRunner.runClient() and ManagedConfigReceiver.onReceive(), and skipping the apply in runClient() when the engine is already running. A simple fix:

🔒 Suggested synchronization
 class EngineRunner {
+    // Shared lock guarding any mutation of the active profile's managed-config state.
+    static final Object MDM_CONFIG_LOCK = new Object();
@@
-            try {
-                io.netbird.gomobile.android.ManagedConfig mdmConfig = ManagedConfigReader.read(context);
-                if (mdmConfig != null && mdmConfig.hasConfig()) {
-                    mdmConfig.apply(configurationFilePath);
+            try {
+                io.netbird.gomobile.android.ManagedConfig mdmConfig = ManagedConfigReader.read(context);
+                if (mdmConfig != null && mdmConfig.hasConfig()) {
+                    synchronized (MDM_CONFIG_LOCK) {
+                        mdmConfig.apply(configurationFilePath);
+                    }

And use the same MDM_CONFIG_LOCK around config.apply(configPath) in ManagedConfigReceiver.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java` around lines 97
- 124, There is a race between EngineRunner.runClient()'s call to
mdmConfig.apply(configurationFilePath) and ManagedConfigReceiver.onReceive()
mutating the same config; serialize these operations by introducing a shared
lock (e.g., a static final Object MDM_CONFIG_LOCK) used by both
EngineRunner.runClient() and ManagedConfigReceiver.onReceive() around any call
to mdmConfig.apply(configPath), and in runClient() check whether the engine is
already running (or a flag like engineStarted) and skip the apply when the
engine is active to avoid mid-run file writes; update references to
mdmConfig.apply(configurationFilePath), ManagedConfigReceiver.onReceive(), and
the engine start calls (goClient.runSync/goClient.runAsync) to use the
lock/flag.

Comment on lines +107 to +120
if (mdmConfig.hasSetupKey()) {
try {
io.netbird.gomobile.android.Auth auth =
io.netbird.gomobile.android.Android.newAuth(configurationFilePath, "");
if (auth != null) {
auth.loginWithSetupKeySync(mdmConfig.getSetupKey(), DeviceName.getDeviceName());
Log.i(LOGTAG, "MDM: silent setup key registration completed");
}
} catch (Exception e) {
// Setup key login may fail if already registered or key expired.
// This is not fatal — continue with normal flow.
Log.w(LOGTAG, "MDM: setup key login skipped or failed: " + e.getMessage());
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Setup-key login is re-attempted on every engine start.

Because MDM config is applied on each runClient() call and mdmConfig.hasSetupKey() will stay true as long as the admin leaves the key configured, loginWithSetupKeySync will be invoked every time the VPN service starts. The failure path is caught, but this generates a round-trip to the management server (and log noise) on every launch even for already-enrolled devices.

Consider gating the login attempt on "engine is not yet enrolled" state (e.g., check device registration / presence of peer key in the active profile) rather than solely on hasSetupKey().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java` around lines 107
- 120, The setup-key login is currently attempted on every
EngineRunner.runClient() because it only checks mdmConfig.hasSetupKey(); change
this to first detect whether the engine/device is already enrolled (e.g., check
the active profile for an existing peer key or a device registration flag) and
only call
io.netbird.gomobile.android.Android.newAuth(...).loginWithSetupKeySync(...) when
not enrolled. In practice modify the block referencing mdmConfig.hasSetupKey()
in EngineRunner to add a precondition like "if mdmConfig.hasSetupKey() &&
!isEnrolled()" (or an equivalent check against the active profile/peer key)
before invoking loginWithSetupKeySync so successful prior enrollments skip the
remote round-trip and log noise.

Comment on lines +109 to +114
io.netbird.gomobile.android.Auth auth =
io.netbird.gomobile.android.Android.newAuth(configurationFilePath, "");
if (auth != null) {
auth.loginWithSetupKeySync(mdmConfig.getSetupKey(), DeviceName.getDeviceName());
Log.i(LOGTAG, "MDM: silent setup key registration completed");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Try to locate the generated binding signature or any other call sites of newAuth.
rg -nP -C3 '\bnewAuth\s*\('
rg -nP --type=go -C3 '\bfunc\s+NewAuth\b|\bAuth\s+struct\b' || true

Repository: netbirdio/android-client

Length of output: 2016


🏁 Script executed:

#!/bin/bash
# Check mdmConfig to see if it contains server address
rg -nP -C5 'mdmConfig' tool/src/main/java/io/netbird/client/tool/EngineRunner.java | head -100

# Look for any server address constants or defaults
rg -nP 'managementServer|serverAddress|baseURL|apiUrl' app/src/main/java/ --max-count 20

# Check if there are other Android.newAuth calls
rg -nP 'newAuth' tool/src/main/java/ app/src/main/java/ | wc -l
rg -nP 'newAuth' --context 5 tool/src/main/java/ app/src/main/java/

Repository: netbirdio/android-client

Length of output: 7692


🏁 Script executed:

#!/bin/bash
# Check if mdmConfig has any server address field
rg -nP 'class ManagedConfig|interface ManagedConfig' -A 20

# Look for Preferences.defaultServer()
rg -nP 'defaultServer|LOGTAG' app/src/main/java/io/netbird/client/ui/ tool/src/main/java/ --max-count 15

# Search for ManagedConfigReader and see what it reads
rg -nP 'class ManagedConfigReader' -A 30 

# Check if there's any documentation about the second parameter
rg -nP 'newAuth.*comment|newAuth.*//|newAuth.*\*' -B 2 -A 2

Repository: netbirdio/android-client

Length of output: 17522


🏁 Script executed:

#!/bin/bash
# Check what Preferences.defaultServer() returns
rg -nP 'public static String defaultServer' -A 5 app/src/main/java/io/netbird/client/tool/Preferences.java

# Look for any comments about empty string in MDM flow
rg -nP 'MDM|setup.?key' -B 2 -A 2 tool/src/main/java/io/netbird/client/tool/EngineRunner.java

# Check if ManagedConfig has any server-related fields/methods
rg -nP 'getManagementServer|serverAddress|server' tool/src/main/java/io/netbird/client/tool/ --type=java

# Look for any explanation of the second parameter in comments/docs
rg -nP '//.*newAuth|/\*.*newAuth' -B 2 -A 2

Repository: netbirdio/android-client

Length of output: 2345


🏁 Script executed:

#!/bin/bash
# Find Preferences.java
fd -type f -name 'Preferences.java'

# Search for defaultServer method anywhere
rg -nP 'defaultServer' -B 2 -A 5

Repository: netbirdio/android-client

Length of output: 3484


🏁 Script executed:

#!/bin/bash
# Look for git history of this line
git log -p --follow -S 'newAuth(configurationFilePath, "")' -- tool/src/main/java/io/netbird/client/tool/EngineRunner.java 2>/dev/null | head -60 || echo "No git history found"

# Look for any TODOs or FIXMEs around the MDM code
rg -nP 'TODO|FIXME|XXX|HACK' -B 2 -A 2 tool/src/main/java/io/netbird/client/tool/EngineRunner.java

# Check git log summary for this file
git log --oneline tool/src/main/java/io/netbird/client/tool/EngineRunner.java 2>/dev/null | head -20 || true

Repository: netbirdio/android-client

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Check the specific commit that added this code
git show 045d922 --stat | head -40

# Also show the full diff of that commit for EngineRunner.java
git show 045d922 -- tool/src/main/java/io/netbird/client/tool/EngineRunner.java | head -150

Repository: netbirdio/android-client

Length of output: 9100


Use Preferences.defaultServer() for the management server address in MDM setup-key enrollment.

Passing an empty string here is inconsistent with ChangeServerFragmentViewModel, which explicitly uses Preferences.defaultServer() ("https://api.netbird.io") when enrolling with a setup key. Either pass the default server address directly:

io.netbird.gomobile.android.Auth auth =
    io.netbird.gomobile.android.Android.newAuth(configurationFilePath, Preferences.defaultServer());

Or document in code why the empty string is intentional and what the Go SDK does with it (e.g., whether it has a built-in fallback).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java` around lines 109
- 114, The MDM setup-key enrollment is calling
Android.newAuth(configurationFilePath, "") which passes an empty server address;
update EngineRunner to use Preferences.defaultServer() instead (i.e., call
Android.newAuth with Preferences.defaultServer()) so behavior matches
ChangeServerFragmentViewModel, or add a brief comment next to the
Android.newAuth call documenting why an empty string is intentional and what the
Go SDK fallback behavior is; locate the call in EngineRunner where
io.netbird.gomobile.android.Auth auth is created and adjust the second argument
accordingly while leaving the subsequent
auth.loginWithSetupKeySync(mdmConfig.getSetupKey(), DeviceName.getDeviceName())
unchanged.

Comment thread tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java Outdated
@dbrieck
Copy link
Copy Markdown
Author

dbrieck commented Apr 24, 2026

Thank you, I'll review these automated code review comments.

- EngineRunner: add MDM_CONFIG_LOCK to serialize config apply operations
- EngineRunner: pass management URL (not empty string) to newAuth
- ManagedConfigReceiver: use goAsync() + executor to avoid main-thread ANR
- ManagedConfigReceiver: use shared MDM_CONFIG_LOCK for concurrency safety
- app_restrictions.xml: use @string/ resource references for localization
- strings.xml: add MDM restriction title/description string resources
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
tool/src/main/java/io/netbird/client/tool/EngineRunner.java (1)

116-133: ⚠️ Potential issue | 🟡 Minor

Silent setup-key login still runs on every engine start.

With the lock fix in place, this block now runs on every runClient() invocation as long as the admin leaves setupKey in the MDM payload. For already-enrolled devices this triggers an unnecessary round-trip to the management server on each VPN start and produces log noise. Consider gating on a "not yet enrolled" signal (presence of a peer key in the active profile / device registration flag) rather than solely on mdmConfig.hasSetupKey().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java` around lines 116
- 133, The silent setup-key login runs on every runClient() because it only
checks mdmConfig.hasSetupKey(); change EngineRunner to first verify the device
is not already enrolled before calling
io.netbird.gomobile.android.Android.newAuth(...) and
auth.loginWithSetupKeySync(...). Specifically, gate the block by inspecting the
active profile/registration state (e.g., check for a peer key or a device
registration flag on the current profile returned by the engine/profile APIs)
and only attempt setup-key login when that check indicates the device is
unregistered; keep the existing try/catch/logging around
auth.loginWithSetupKeySync and DeviceName.getDeviceName().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java`:
- Around line 118-125: EngineRunner is passing mdmConfig.getSetupKey() as the
second arg to io.netbird.gomobile.android.Android.newAuth(...) which expects the
management server URL; change the call in EngineRunner to pass the management
URL instead (e.g., mdmConfig.getManagementURL()), and if that getter is
null/empty fall back to Preferences.defaultServer() or rely on the
configurationFilePath-only behavior; update the Android.newAuth(...) invocation
accordingly so it receives the correct server URL rather than the setup key, and
leave the subsequent auth.loginWithSetupKeySync(...) call unchanged.

---

Duplicate comments:
In `@tool/src/main/java/io/netbird/client/tool/EngineRunner.java`:
- Around line 116-133: The silent setup-key login runs on every runClient()
because it only checks mdmConfig.hasSetupKey(); change EngineRunner to first
verify the device is not already enrolled before calling
io.netbird.gomobile.android.Android.newAuth(...) and
auth.loginWithSetupKeySync(...). Specifically, gate the block by inspecting the
active profile/registration state (e.g., check for a peer key or a device
registration flag on the current profile returned by the engine/profile APIs)
and only attempt setup-key login when that check indicates the device is
unregistered; keep the existing try/catch/logging around
auth.loginWithSetupKeySync and DeviceName.getDeviceName().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 209f6e5f-93fa-4663-b2af-2536a0da5217

📥 Commits

Reviewing files that changed from the base of the PR and between 045d922 and 6ec5073.

📒 Files selected for processing (4)
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/app_restrictions.xml
  • tool/src/main/java/io/netbird/client/tool/EngineRunner.java
  • tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/res/xml/app_restrictions.xml
  • tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java

Comment thread tool/src/main/java/io/netbird/client/tool/EngineRunner.java
Use mdmConfig.getManagementURL() so NewAuth connects to the correct
MDM-specified server instead of using the setup key as the URL.
@pappz pappz self-assigned this Apr 24, 2026
@pappz
Copy link
Copy Markdown
Collaborator

pappz commented Apr 24, 2026

Hello @dbrieck!
Thank you for your PR! Which MDM have you tested it with?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants