From b7aeec1f2340d62594d85196c7598445f65666dc Mon Sep 17 00:00:00 2001 From: riccardom Date: Tue, 9 Jun 2026 09:18:30 +0200 Subject: [PATCH 1/4] Adds doc for Windows/macOS MDM integration --- src/components/NavigationDocs.jsx | 1 + src/pages/client/mdm-integration.mdx | 390 +++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 src/pages/client/mdm-integration.mdx diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx index 094a7db6..3d8edbf9 100644 --- a/src/components/NavigationDocs.jsx +++ b/src/components/NavigationDocs.jsx @@ -746,6 +746,7 @@ export const docsNavigation = [ links: [ { title: 'Profiles', href: '/client/profiles' }, { title: 'Environment Variables', href: '/client/environment-variables' }, + { title: 'MDM Integration', href: '/client/mdm-integration' }, { title: 'Settings', isOpen: false, diff --git a/src/pages/client/mdm-integration.mdx b/src/pages/client/mdm-integration.mdx new file mode 100644 index 00000000..7f31d473 --- /dev/null +++ b/src/pages/client/mdm-integration.mdx @@ -0,0 +1,390 @@ +import { Note, Warning } from "@/components/mdx"; + +export const description = + "Enforce NetBird client configuration through your MDM channel (Intune, Jamf, Kandji, Mosyle, Workspace ONE, JumpCloud, Group Policy). Push management URL, kill switches, and other settings to a fleet of Windows and macOS devices."; + +# MDM Integration + +NetBird's client honors policies pushed by your Mobile Device Management +(MDM) channel, so an administrator can enforce configuration across a +fleet of devices instead of touching each machine. On every supported +platform the daemon reads from the **OS-native managed-configuration +store** that your MDM already writes to. No agent of ours sits between +you and the MDM provider; whatever you can push to that store (manually, +via Group Policy, via a Configuration Profile, via your MDM console) +becomes effective NetBird policy. + +This page covers Windows and macOS. iOS and Android support is on the +roadmap. + +## At a glance + +| Platform | Where NetBird reads policy | How an admin writes it | +| --- | --- | --- | +| Windows | `HKLM\Software\Policies\NetBird` (registry) | Group Policy (ADMX) · Intune ADMX ingestion or OMA-URI · `reg import` · MDM-vendor scripts | +| macOS | `/Library/Managed Preferences/io.netbird.client.plist` | Configuration Profile (`.mobileconfig`) pushed by your MDM, targeting bundle id `io.netbird.client` | + +Both backends are the de-facto convention for desktop apps (the same +shape Chrome, Edge, Firefox, Zoom, Tailscale, Citrix Workspace, and +others use). Any MDM that supports the platform also supports NetBird — +there is no NetBird-specific integration to build. + +## How enforcement works + +When NetBird starts, and every minute while it runs, the daemon: + +1. Reads the platform-native managed-configuration store. +2. Merges the values **on top of every other configuration layer** + (defaults → on-disk profile → environment variables → CLI/UI input → + **MDM**). MDM always wins. +3. Locks any field that came from the MDM source. Attempts to change + that field from the GUI, the CLI (`netbird up --flag=...`, `netbird + login --flag=...`) or via direct gRPC are rejected with a clear + error listing the locked fields. The client UI greys these fields + out and tags them with **(MDM)** so the user knows they cannot + change them. +4. If the MDM payload changes (admin pushes new values), the change + takes effect within ~1 minute on the device — no client restart + needed. + + +MDM is **authoritative**. When a key is present in the MDM payload, the +MDM value wins regardless of whether it is `true` or `false`. An admin +pushing `disableNetworks=false` via MDM re-enables the feature even on a +host that was installed with `--disable-networks`. The MDM payload is +the source of truth as long as the policy is in place. MDM-supplied +values are also never written back to the on-disk profile, so removing +the policy at the MDM side takes effect on the next reload with no +stale residue on the device. + + +## Policy keys reference + +The same 16 keys apply on every platform. Names are camelCase in the +managed-configuration payload; the Windows ADMX template renders the +PascalCase variant in the Group Policy Editor — both are recognized. + +| Key | Type | Description | +| --- | --- | --- | +| `managementURL` | string | Override the management server URL (e.g. `https://api.netbird.io:443` or a self-hosted URL). | +| `preSharedKey` | string | WireGuard pre-shared key. Treated as secret and redacted in logs. | +| `wireguardPort` | integer | UDP port the local WireGuard interface binds to. Range `1–65535`. | +| `allowServerSSH` | boolean | Allow the embedded NetBird SSH server on this peer. | +| `disableAutoConnect` | boolean | Skip auto-connecting on startup; require an explicit `netbird up`. | +| `rosenpassEnabled` | boolean | Turn on the post-quantum Rosenpass key exchange. | +| `rosenpassPermissive` | boolean | Permissive mode for Rosenpass (interop with non-Rosenpass peers). | +| `blockInbound` | boolean | Drop all inbound traffic except established/related — kill-switch style. | +| `disableClientRoutes` | boolean | This peer does not route traffic to other peers. | +| `disableServerRoutes` | boolean | This peer is not a router for others. | +| `disableMetricsCollection` | boolean | Disable anonymous usage telemetry. | +| `disableUpdateSettings` | boolean | Block every configuration change from UI or CLI on this device (read-only mode). | +| `disableProfiles` | boolean | Hide the profile menu in the GUI and reject profile CRUD via CLI. | +| `disableNetworks` | boolean | Hide the Networks / Exit Node menus in the GUI and reject the related RPCs. | +| `splitTunnelMode` | string | `allow` or `disallow` — split-tunnel policy mode (Android only at the client level; harmless on desktop). | +| `splitTunnelApps` | string | Comma-separated list of package names that the split-tunnel mode applies to (Android only). | + +### Notes on a few keys + +- `disableUpdateSettings` and `disableProfiles` overlap with the + service-install CLI flags `--disable-update-settings` and + `--disable-profiles`. Either source can disable the feature; the MDM + value wins when present. +- `disableUpdateSettings` keeps the Settings view in the GUI visible + (so users can inspect current values) but rejects every attempt to + save changes. Use it for read-only fleets. +- `splitTunnelMode` and `splitTunnelApps` are wired into Android's + `VpnService.Builder.addAllowedApplication()` flow; on Windows and + macOS the daemon parses the keys but ignores them. They are safe to + ship in a cross-platform payload. +- The `disableMetricsCollection` key is reserved for an upcoming + metrics integration; the client recognizes it today but no metrics + pipeline is shipped yet. + +## Windows + +The NetBird daemon reads policies from +`HKLM\Software\Policies\NetBird`. Anything that ends up under that +registry key — through whichever delivery channel — becomes policy. +The shapes are: + +| Value name | Registry type | Example | +| --- | --- | --- | +| `ManagementURL`, `PreSharedKey`, `SplitTunnelMode`, `SplitTunnelApps` | `REG_SZ` | `"https://api.netbird.io:443"` | +| All `Disable*` flags, `AllowServerSSH`, `RosenpassEnabled`, `RosenpassPermissive` | `REG_DWORD` (0 / 1) | `0x00000001` | +| `WireguardPort` | `REG_DWORD` | `0x0000ca6c` (51820 decimal) | + +Choose one of the delivery channels below. All four converge on the +same registry key. + +### Group Policy (on-prem AD / local gpedit) + +1. Copy the ADMX/ADML files into the system Policy Definitions store: + - Place `netbird.admx` in `C:\Windows\PolicyDefinitions\`. + - Place `netbird.adml` in `C:\Windows\PolicyDefinitions\en-US\`. +2. Open `gpedit.msc` (or the AD Group Policy Management Editor). +3. Navigate to **Computer Configuration → Administrative Templates → + NetBird**. +4. Edit any policy (e.g. **Management URL**), set it to **Enabled** + with the desired value, and click **OK**. +5. Run `gpupdate /force` on each target device (or wait for the + periodic refresh). +6. Verify with `reg query HKLM\Software\Policies\NetBird` — the values + you set should appear there. + +The ADMX template is shipped in the NetBird repo at +`docs/netbird.admx` / `docs/netbird.adml`. + +### Microsoft Intune (ADMX ingestion) + +Recommended for cloud-managed Windows fleets. + +1. In the Intune admin center, go to **Devices → Configuration → Import + ADMX**, upload `netbird.admx` together with `netbird.adml`. Wait for + the **Available** status. +2. Create a new **Configuration Profile → Templates → Imported + Administrative templates → NetBird**. +3. Configure the policies you want to enforce. +4. Assign the profile to your device group(s) and save. + +Devices pick up the policy on the next Intune sync (typically within +8 hours, sooner if you trigger a manual sync from the device). The +values end up in `HKLM\Software\Policies\NetBird`. + +### Microsoft Intune (custom OMA-URI) + +If you cannot ingest the ADMX template, you can push individual values +via OMA-URI under +`./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/...` or via +the Registry CSP at +`./Device/Vendor/MSFT/Registry/HKEY_LOCAL_MACHINE/Software/Policies/NetBird/`. +ADMX ingestion is simpler and gives admins the same UI as on-prem GPO, +so prefer that. + +### `.reg` import (single source of truth) + +For fleets without an MDM, or as a quick-test path, you can carry the +whole policy in a single `.reg` file: + +1. Configure the policy values on a reference machine (via `gpedit` or + `reg add`). +2. Export the key: + ``` + reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y + ``` +3. Distribute the resulting file and apply with: + ``` + reg import netbird-policy.reg + ``` + +A sample is in the NetBird repo at `docs/netbird-policy.reg`. + +### JumpCloud + +NetBird ships a JumpCloud companion script at +`docs/netbird-policy.reg.ps1`. To use it: + +1. In the JumpCloud admin console, go to **Device Management → + Commands → +**. +2. Type: **Windows PowerShell**. Run as: **SYSTEM**. +3. Paste `netbird-policy.reg.ps1` verbatim into the command body. +4. In the same command, attach the `netbird-policy.reg` file you + produced above. JumpCloud copies attached files into the command's + working directory before invoking the script. +5. Bind the command to the target system group and run it. + +The script wipes the existing `HKLM\Software\Policies\NetBird` key +before importing the `.reg`, so the `.reg` is the **single source of +truth** for that device. To unset all policy, attach an empty (header- +only) `.reg`; the daemon will pick up the absence on the next reload. + +## macOS + +The NetBird daemon reads policy from +`/Library/Managed Preferences/io.netbird.client.plist`. macOS writes +that file when an MDM provider pushes a Configuration Profile whose +`com.apple.ManagedClient.preferences` payload targets the bundle id +`io.netbird.client`. + + +macOS wipes the contents of `/Library/Managed Preferences/` on every +boot if the device is not MDM-enrolled. Manual `defaults write` works +for a quick test but does not survive a reboot on an un-enrolled Mac. +Use a real MDM channel for production rollouts. + + +### Custom Configuration Profile (recommended) + +This is the canonical macOS path and works with every MDM +(Jamf, Kandji, Mosyle, Microsoft Intune for Mac, Workspace ONE, +JumpCloud, Apple Configurator 2, etc.). + +1. Start from the template at `docs/netbird-macos.mobileconfig` in the + NetBird repo. Open it in your editor (or in + [iMazing Profile Editor](https://imazing.com/profile-editor) / + [ProfileCreator](https://github.com/ProfileCreator/ProfileCreator)). +2. Inside the `mcx_preference_settings` dictionary, set the keys you + want to enforce. Keep the bundle id `io.netbird.client` as the + preference domain. +3. Replace the placeholder `PayloadUUID` values with freshly generated + UUIDs (`uuidgen` on macOS) so each deployment has unique ids. +4. (Optional, recommended for production) sign the profile with your + organization's Developer ID Installer certificate using + `productsign` — unsigned profiles on Sonoma/Sequoia/Tahoe require + an extra user confirmation on install. +5. Upload the resulting `.mobileconfig` to your MDM as a **Custom + Configuration Profile** and scope it to the target device group. + +Verify on a target device with: + +```bash +sudo defaults read "/Library/Managed Preferences/io.netbird.client" +``` + +The output should match the keys you set in the profile. + +### MDM-specific notes + +- **Jamf Pro**: upload as **Computers → Configuration Profiles → New → + Application & Custom Settings → External Applications → Upload File + (Plist file)** for the preference domain `io.netbird.client`. +- **Kandji**: use the **Custom Profile** assignment library item. +- **Mosyle**: **Profiles → Add new profile → Custom Settings** with + domain `io.netbird.client`. +- **Microsoft Intune (for Mac)**: **Devices → Configuration → Create + profile → macOS → Templates → Custom**, upload the `.mobileconfig`. +- **Apple Configurator 2** (no MDM, ideal for testing on a tethered + device): drag the `.mobileconfig` onto the device in Configurator and + push. + +### JumpCloud + +JumpCloud supports two delivery channels for the NetBird policy on +macOS. Pick whichever fits how your fleet is enrolled. + +#### MDM Custom Configuration Profile (recommended for MDM-enrolled fleets) + +If your Macs are MDM-enrolled with JumpCloud, push the policy as a +managed-preferences plist: + +1. In the JumpCloud admin console, open **Policy Management → + Policies → +** and choose the **Mac** platform. +2. Pick the **MDM Custom Configuration Profile** policy template. +3. Upload `docs/io.netbird.client.plist` from the NetBird repository + as the plist payload. Edit the file before upload to enable just + the keys you want to enforce — leave the rest commented out. +4. Bind the policy to the target Device Group and save. + +Notes: + +- JumpCloud's **MDM Custom Configuration Profile** accepts a bare + managed-preferences plist (the inner Apple managed-prefs dictionary) + — **not** a full `.mobileconfig` envelope. Uploading + `netbird-macos.mobileconfig` will be rejected. Use the bare + `io.netbird.client.plist` for this code path; reserve + `netbird-macos.mobileconfig` for other MDMs that expect the full + Configuration Profile shape. +- Keep the filename as `io.netbird.client.plist`. The Apple + convention for managed-preferences plists is + `.plist` (this is how macOS materializes the file at + `/Library/Managed Preferences/.plist`), and JumpCloud's + policy form does not currently expose a separate bundle-identifier + field — keeping the canonical filename is the safest path. If your + JumpCloud console version surfaces a bundle-id / preference-domain + field elsewhere in the policy wizard, set it to `io.netbird.client` + too. + +JumpCloud wraps the plist into an Apple Configuration Profile and +pushes it via the MDM channel. The OS materializes the file at +`/Library/Managed Preferences/io.netbird.client.plist`, where the +NetBird daemon picks it up within the next 1-minute reload tick. +Removing the policy from JumpCloud removes the file on the next sync, +which un-locks the corresponding fields on the client. + +#### Shell Command (no MDM enrollment required) + +If your fleet is JumpCloud-managed but not MDM-enrolled, NetBird ships +a companion script at `docs/netbird-macos.sh`. It is the macOS +counterpart of the Windows `.reg.ps1` script — same fleet, different +backend: + +1. Edit the `### POLICY VALUES ###` block at the top of the script; + set the variables for the keys you want to enforce and leave the + rest at `$NULL`. +2. In the JumpCloud admin console, go to **Device Management → + Commands → +**. Type: **Mac, Shell**. Run as: **root**. +3. Paste the edited script verbatim into the command body. +4. Bind to the target system group and run. + +The script writes +`/Library/Managed Preferences/io.netbird.client.plist`, sets ownership +to `root:wheel` with mode `644`, and kicks the NetBird daemon so the +change applies immediately. On MDM-enrolled devices the file survives +reboots; on un-enrolled devices the file is wiped at the next reboot +(macOS-imposed). Prefer the Custom Mac Application Settings policy +above when the fleet is enrolled. + +## Verifying enforcement + +On any platform, the cleanest verification is the daemon's own debug +dump: + +```bash +netbird debug config +``` + +The response includes a `mDMManagedFields` array that lists every key +the daemon is currently honoring from the MDM source. If a key you +expected to be locked is missing from that array, the MDM payload did +not reach the device (or used a value name the daemon does not +recognize). + +The client UI mirrors the same state: any submenu item, settings field, +or kill switch driven by MDM appears greyed out with a **(MDM)** tag +next to its label. + +Daemon logs (`/var/log/netbird/client.log` on Linux/macOS, +`%ProgramData%\Netbird\` on Windows) contain a one-line +`MDM enrolled with N managed key(s): [...]` entry on every reload, plus +one `MDM override = ` line per applied key. Secrets are +redacted. + +## Troubleshooting + +**The policy did not apply at all.** +Check that the daemon can see the source. + +- Windows: `reg query HKLM\Software\Policies\NetBird` — if empty, the + delivery channel did not write the values. Check `gpresult /h` for + GPO failures or the Intune sync status in **Settings → Accounts → + Access work or school → Info → Sync**. +- macOS: + `sudo defaults read /Library/Managed\ Preferences/io.netbird.client` + — if the file is missing, the MDM payload was not pushed or the + bundle id in the profile does not match `io.netbird.client`. + +**The key shows up in the registry / plist but not in `netbird debug +config` `mDMManagedFields`.** +The value name is misspelled. Names are case-insensitive but must match +one of the keys in the reference table above. The daemon log emits an +`MDM ignoring unknown ` warning when this happens. + +**The user can still change the field from the GUI / CLI.** +The change is being rejected by the daemon but the UI may not have +caught up yet. The UI refreshes within a couple of seconds after a +config change; try closing and reopening the Settings window. If the +change actually sticks, double-check that the MDM payload is still +present on the device — it may have been removed by another policy. + +**On macOS, the file disappears after a reboot.** +The device is not MDM-enrolled. macOS protects +`/Library/Managed Preferences/` by wiping it at boot if no MDM +controls the directory. Enroll the device with a real MDM provider for +persistent rollouts. + +**My MDM provider is not in the list above.** +Any MDM that can push a Configuration Profile on macOS or write a +registry value on Windows works. The mechanism is OS-native, not +NetBird-specific. If you hit a quirk specific to your provider, please +open an issue at +https://github.com/netbirdio/netbird/issues with the provider name and +what you observed. From 1e6a964f324cf546ec8f55f0d8326f3456c919b9 Mon Sep 17 00:00:00 2001 From: riccardom Date: Fri, 19 Jun 2026 22:21:40 +0200 Subject: [PATCH 2/4] Fixes coderabbit suggestions --- .../docs-static/files/io.netbird.client.plist | 126 ++++++++++ .../files/netbird-macos.mobileconfig | 159 +++++++++++++ public/docs-static/files/netbird-macos.sh | 189 +++++++++++++++ public/docs-static/files/netbird-policy.reg | Bin 0 -> 1418 bytes .../docs-static/files/netbird-policy.reg.ps1 | 94 ++++++++ public/docs-static/files/netbird.adml | 95 ++++++++ public/docs-static/files/netbird.admx | 223 ++++++++++++++++++ src/pages/client/mdm-integration.mdx | 15 +- 8 files changed, 892 insertions(+), 9 deletions(-) create mode 100644 public/docs-static/files/io.netbird.client.plist create mode 100644 public/docs-static/files/netbird-macos.mobileconfig create mode 100644 public/docs-static/files/netbird-macos.sh create mode 100644 public/docs-static/files/netbird-policy.reg create mode 100644 public/docs-static/files/netbird-policy.reg.ps1 create mode 100644 public/docs-static/files/netbird.adml create mode 100644 public/docs-static/files/netbird.admx diff --git a/public/docs-static/files/io.netbird.client.plist b/public/docs-static/files/io.netbird.client.plist new file mode 100644 index 00000000..f42b6b3d --- /dev/null +++ b/public/docs-static/files/io.netbird.client.plist @@ -0,0 +1,126 @@ + + + + + + + + managementURL + https://api.netbird.io:443 + + + + + + + allowServerSSH + + + + + + + + + + + + + + + diff --git a/public/docs-static/files/netbird-macos.mobileconfig b/public/docs-static/files/netbird-macos.mobileconfig new file mode 100644 index 00000000..53453db5 --- /dev/null +++ b/public/docs-static/files/netbird-macos.mobileconfig @@ -0,0 +1,159 @@ + + + + + + + PayloadType + Configuration + PayloadVersion + 1 + PayloadIdentifier + io.netbird.client.mdm + PayloadUUID + 11111111-1111-1111-1111-111111111111 + PayloadDisplayName + NetBird MDM Policy + PayloadDescription + Enforces NetBird client configuration. Values written here override any local user / CLI / on-disk setting and are re-applied at every daemon boot and on every 1-minute MDM reload tick. + PayloadOrganization + NetBird + PayloadScope + System + PayloadRemovalDisallowed + + + PayloadContent + + + + PayloadType + com.apple.ManagedClient.preferences + PayloadVersion + 1 + PayloadIdentifier + io.netbird.client.mdm.preferences + PayloadUUID + 22222222-2222-2222-2222-222222222222 + PayloadDisplayName + NetBird Managed Preferences + PayloadEnabled + + + PayloadContent + + io.netbird.client + + Forced + + + mcx_preference_settings + + + + managementURL + https://api.netbird.io:443 + + + + + + + allowServerSSH + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs-static/files/netbird-macos.sh b/public/docs-static/files/netbird-macos.sh new file mode 100644 index 00000000..a2f5ff5e --- /dev/null +++ b/public/docs-static/files/netbird-macos.sh @@ -0,0 +1,189 @@ +#!/bin/bash +# +# SYNOPSIS +# Push the NetBird MDM policy to a macOS device via JumpCloud Commands. +# +# DESCRIPTION +# This is the macOS counterpart of docs/netbird-policy.reg.ps1. +# It writes the values declared in the "POLICY VALUES" block below to +# the managed-preferences plist that the NetBird daemon's +# client/mdm/policy_darwin.go loader reads on every 1-minute MDM +# reload tick: +# +# /Library/Managed Preferences/io.netbird.client.plist +# +# Once the plist lands, the daemon picks up the new values without +# restart (the ticker calls Config.apply() → applyMDMPolicy() and +# restarts the engine on diff). +# +# DEPLOYMENT (JumpCloud) +# 1. Admin Console -> Device Management -> Commands -> +. +# 2. Type: Mac, Shell, Run as: root. +# 3. Paste this file verbatim into the command body. +# 4. Bind to the target system group, save, run. +# +# IMPORTANT: PERSISTENCE +# macOS wipes /Library/Managed Preferences/ at every boot on devices +# that are NOT MDM-enrolled. For a persistent fleet rollout, push the +# companion docs/netbird-macos.mobileconfig as a Custom Configuration +# Profile (Admin Console -> MDM -> Mac Custom Configuration Profiles) +# instead of this script. Use this script when: +# - the device is MDM-enrolled (file survives reboots), or +# - you need a one-shot test push before reboot, or +# - you orchestrate via JumpCloud Commands and want the same +# variable-driven workflow as the Windows .ps1 sibling. +# +# IDEMPOTENCY: re-running with the same values is a no-op from the +# daemon's point of view (the 1-minute reload ticker diff returns empty). +# +# SECURITY: PreSharedKey is redacted in this script's log output. + +set -euo pipefail + +### POLICY VALUES — EDIT THIS BLOCK ########################################### +# +# Set each variable below to the desired value. Set to empty string "" +# or to NULL to omit a key entirely (the daemon treats an absent key +# as "no enforcement" for that field). Booleans use "true"/"false" +# (lowercase). Integers as decimal. +# +# Reference for key names + accepted values: +# client/mdm/policy.go (Key* constants) +# docs/netbird-macos.mobileconfig (sample profile) +# docs/netbird.admx + .adml (Windows ADMX schema) +# +NULL='__UNSET__' +managementURL='https://api.netbird.io:443' +preSharedKey="$NULL" # secret; redacted in log +allowServerSSH='true' +blockInbound="$NULL" +disableAutoConnect="$NULL" +disableClientRoutes="$NULL" +disableServerRoutes="$NULL" +disableMetricsCollection="$NULL" +disableUpdateSettings="$NULL" +disableProfiles="$NULL" +disableNetworks="$NULL" +rosenpassEnabled="$NULL" +rosenpassPermissive="$NULL" +wireguardPort='51820' +splitTunnelMode="$NULL" # "allow" or "disallow", Android-only at the daemon level +splitTunnelApps="$NULL" # comma-separated app IDs, Android-only +############################################################################## + +readonly PLIST_DIR='/Library/Managed Preferences' +readonly PLIST_PATH="$PLIST_DIR/io.netbird.client.plist" +readonly LOG_TAG='netbird-mdm' + +# log sends a message to the system logger using the configured tag and echoes the message to stdout prefixed by an ISO 8601 UTC timestamp and the tag. +log() { + /usr/bin/logger -t "$LOG_TAG" "$*" + printf '%s [%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$LOG_TAG" "$*" +} + +# is_set returns success if the provided value is non-empty and is not equal to the special NULL marker. +is_set() { + local value="$1" + [[ -n "$value" && "$value" != "$NULL" ]] +} + +# start_plist creates the temporary plist file at "$PLIST_PATH.tmp" containing the XML plist header and opening `` for the policy plist. +start_plist() { + cat > "$PLIST_PATH.tmp" <<'EOF' + + + + +EOF +} + +# end_plist appends the closing `` and `` tags to the temporary plist file. +end_plist() { + cat >> "$PLIST_PATH.tmp" <<'EOF' + + +EOF +} + +# emit_string appends a plist ``/`` entry for the given key and value to "$PLIST_PATH.tmp", XML-escaping `&`, `<`, and `>`, and logs the assignment (masking the logged value as `********** (secret)` when the key is `preSharedKey`). +emit_string() { + local key="$1" value="$2" log_value="$2" + # Escape XML entities in the value + local escaped + escaped="$(printf '%s' "$value" | sed -e 's/&/\&/g' -e 's//\>/g')" + printf ' %s\n %s\n' "$key" "$escaped" >> "$PLIST_PATH.tmp" + if [[ "$key" == "preSharedKey" ]]; then + log_value='********** (secret)' + fi + log "set $key = $log_value" +} + +# emit_bool writes a boolean plist entry for a given key into the temporary plist file. +# emit_bool writes a boolean plist entry for a key when the provided value matches an accepted boolean token; logs an error and skips the key on invalid input. +emit_bool() { + local key="$1" value="$2" + local xml_bool + case "$value" in + true|True|TRUE|1|yes) xml_bool='' ; value='true' ;; + false|False|FALSE|0|no) xml_bool='' ; value='false' ;; + *) log "invalid boolean for $key: $value (must be true/false); skipping"; return ;; + esac + printf ' %s\n %s\n' "$key" "$xml_bool" >> "$PLIST_PATH.tmp" + log "set $key = $value" +} + +# emit_int validates that VALUE contains only decimal digits and, if valid, appends an `` plist entry for KEY to the temporary plist (`$PLIST_PATH.tmp`) and logs the assignment; on invalid input it logs a skip and does not emit the key. +emit_int() { + local key="$1" value="$2" + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + log "invalid integer for $key: $value (must be decimal); skipping" + return + fi + printf ' %s\n %s\n' "$key" "$value" >> "$PLIST_PATH.tmp" + log "set $key = $value" +} + +# main builds the NetBird MDM plist from configured policy variables, validates and installs it to /Library/Managed Preferences/io.netbird.client.plist (root:wheel, 644) and optionally triggers the NetBird daemon to reload. +main() { + log "applying NetBird MDM policy to $PLIST_PATH" + /bin/mkdir -p "$PLIST_DIR" + start_plist + + is_set "$managementURL" && emit_string managementURL "$managementURL" + is_set "$preSharedKey" && emit_string preSharedKey "$preSharedKey" + is_set "$allowServerSSH" && emit_bool allowServerSSH "$allowServerSSH" + is_set "$blockInbound" && emit_bool blockInbound "$blockInbound" + is_set "$disableAutoConnect" && emit_bool disableAutoConnect "$disableAutoConnect" + is_set "$disableClientRoutes" && emit_bool disableClientRoutes "$disableClientRoutes" + is_set "$disableServerRoutes" && emit_bool disableServerRoutes "$disableServerRoutes" + is_set "$disableMetricsCollection" && emit_bool disableMetricsCollection "$disableMetricsCollection" + is_set "$disableUpdateSettings" && emit_bool disableUpdateSettings "$disableUpdateSettings" + is_set "$disableProfiles" && emit_bool disableProfiles "$disableProfiles" + is_set "$disableNetworks" && emit_bool disableNetworks "$disableNetworks" + is_set "$rosenpassEnabled" && emit_bool rosenpassEnabled "$rosenpassEnabled" + is_set "$rosenpassPermissive" && emit_bool rosenpassPermissive "$rosenpassPermissive" + is_set "$wireguardPort" && emit_int wireguardPort "$wireguardPort" + is_set "$splitTunnelMode" && emit_string splitTunnelMode "$splitTunnelMode" + is_set "$splitTunnelApps" && emit_string splitTunnelApps "$splitTunnelApps" + + end_plist + + if ! /usr/bin/plutil -lint "$PLIST_PATH.tmp" >/dev/null 2>&1; then + log "ERROR: generated plist failed plutil lint; not installing" + /usr/bin/plutil -lint "$PLIST_PATH.tmp" >&2 || true + /bin/rm -f "$PLIST_PATH.tmp" + exit 1 + fi + + /bin/mv -f "$PLIST_PATH.tmp" "$PLIST_PATH" + /usr/sbin/chown root:wheel "$PLIST_PATH" + /bin/chmod 644 "$PLIST_PATH" + + log "policy installed; NetBird daemon will pick it up within the next 1-minute reload tick" + + # Optional: kick the daemon for an immediate apply. Safe — does + # nothing on a host where NetBird is not yet installed. + /bin/launchctl kickstart -k system/io.netbird.client 2>/dev/null || true +} + +main "$@" diff --git a/public/docs-static/files/netbird-policy.reg b/public/docs-static/files/netbird-policy.reg new file mode 100644 index 0000000000000000000000000000000000000000..ba4402e50f956facd45f368b214ee6fbfc6dcf39 GIT binary patch literal 1418 zcmbu9VNV)C5Qg8+P5K`UpBvQFHYtf8fHtvKDuRbG+K}V!6e1qx4kY^H)n{hW)I`+M zZ1!$TzChI%1!*|Dvik z7$5b)=ZSXo3!7v0wWobGRp;MVi+_`|pZd+|sk+#ofjU+eT4V4h=wzyBn;9b+Bbl={X3MPVU|r!WSX~lt6;?5riLYZn z{{&LONy-1xISt{Ilc%ctF5PeNQ-AW@MOx_Ezg<}GxR(@8hL)=4d9&O-j)2f`=7}!I zRjvWr7G?T|_2weZ?=UNgU;s)+&S!peO;AFn680ADavm=uHNmrc$+7P^rzyLTxqroK zVQJN6cU@8ycJId4xP`h}g<+_ckll+FnYCeMbvbfAt3~(sz5j2fBI+;YHZ9kNJM|1% z<-M1iMP#-^P;8abof)+qD!xN6explwwaKma6j6&2=sn~^q!F489k{%tn%+0814Avh zyq97SGw&R$o(NBS3;#op$U3m5b;cRlpU_nUXIs^+?`zs(e11Z;tXj|IWG7}-!Zv3W zYW}TG32#-!tN1xe8_WWp)aSTrbj$1$^uD5Jd9wKc D>FeT* literal 0 HcmV?d00001 diff --git a/public/docs-static/files/netbird-policy.reg.ps1 b/public/docs-static/files/netbird-policy.reg.ps1 new file mode 100644 index 00000000..011d706d --- /dev/null +++ b/public/docs-static/files/netbird-policy.reg.ps1 @@ -0,0 +1,94 @@ +#requires -Version 5.1 +<# +.SYNOPSIS + Push the NetBird MDM policy to a Windows device via JumpCloud Commands + by importing a sidecar netbird-policy.reg file. + +.DESCRIPTION + Windows counterpart of docs/netbird-macos.sh. Outcome: + HKLM\Software\Policies\NetBird populated from the attached + netbird-policy.reg file, daemon picks up the change via the + 1-minute MDM reload ticker. + + Deployment: + 1. Admin Console -> Device Management -> Commands -> +. + 2. Type: Windows PowerShell. Run as: SYSTEM. + 3. Paste this file verbatim into the command body. + 4. In the same command, attach `netbird-policy.reg` as a file. + JumpCloud copies attached files into the command's working + directory before invoking the script, so `$PSScriptRoot` or + Get-Location resolves to where the .reg lives. + 5. Bind to the target system group, save, run. + + Producing the .reg file: + On a reference machine, after configuring the policy values either + via gpedit (GPO) or manual `reg add`, export with: + + reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y + + Then attach the resulting file to the JumpCloud command. + + Semantics: + - The script nukes the existing HKLM\Software\Policies\NetBird key + before importing the .reg, so the .reg is the SINGLE SOURCE OF + TRUTH. Any value present in the registry but absent from the .reg + is removed. This is what an MDM admin almost always wants. + - Setting the .reg to an empty (header-only) file effectively unsets + the policy. + + Idempotency: re-running the script with the same .reg is a no-op from + the daemon's perspective (values identical → 1-min ticker sees no + diff → engine not restarted). + + Exit codes: 0 = success; 1 = .reg missing or reg.exe error. +#> + +$ErrorActionPreference = "Stop" + +$RegFileName = "netbird-policy.reg" +$RegKey = "HKLM\Software\Policies\NetBird" + +# Resolve the attached .reg file: JumpCloud copies command attachments +# into C:\Windows\Temp\ before invoking the script. Cwd / $PSScriptRoot +# fallbacks cover the local-dev case where you might dot-source this +# from elsewhere. +$candidates = @( + (Join-Path "$env:WINDIR\Temp" $RegFileName) + (Join-Path (Get-Location) $RegFileName) + (Join-Path $PSScriptRoot $RegFileName) +) | Where-Object { Test-Path $_ } + +if ($candidates.Count -eq 0) { + Write-Error "[netbird-mdm] $RegFileName not found in working directory or `$PSScriptRoot. Attach the file to the JumpCloud command." + exit 1 +} +$regFile = $candidates[0] +Write-Host "[netbird-mdm] using $regFile" + +# Wipe the existing policy key so the .reg is authoritative. +$existed = Test-Path "Registry::HKEY_LOCAL_MACHINE\Software\Policies\NetBird" +if ($existed) { + & reg.exe delete $RegKey /f | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Error "[netbird-mdm] failed to clear $RegKey before import (exit $LASTEXITCODE)" + exit 1 + } + Write-Host "[netbird-mdm] cleared previous values under $RegKey" +} + +# Import. reg.exe writes both data and (re-)creates the key if needed. +& reg.exe import $regFile +if ($LASTEXITCODE -ne 0) { + Write-Error "[netbird-mdm] reg import failed (exit $LASTEXITCODE)" + exit 1 +} + +# Audit dump so the JumpCloud per-execution log captures the applied state. +Write-Host "[netbird-mdm] final policy state under $RegKey :" +& reg.exe query $RegKey /s + +# Daemon's 1-min reload ticker picks up the change automatically. +# Uncomment to force immediate convergence (skips the ticker wait): +# Restart-Service netbird -Force -ErrorAction SilentlyContinue + +exit 0 diff --git a/public/docs-static/files/netbird.adml b/public/docs-static/files/netbird.adml new file mode 100644 index 00000000..d49b0502 --- /dev/null +++ b/public/docs-static/files/netbird.adml @@ -0,0 +1,95 @@ + + + NetBird Client Policies + Group Policy template for NetBird client MDM-managed settings. Values are written under HKLM\Software\Policies\NetBird and consumed by the netbird daemon at startup and every 1-minute reload tick. + + + + + NetBird + NetBird Client 0.40+ + + + Management URL + URL of the NetBird management server. Format: https://host[:port]. When set, users cannot override this value via UI or CLI. + + Pre-shared key + WireGuard pre-shared key used as an additional symmetric secret on every peer-to-peer tunnel. Secret value. + + + Disable auto-connect + When enabled, the NetBird tunnel does not auto-connect at daemon startup. Equivalent to --disable-auto-connect. + + Disable client routes + When enabled, this client will not consume routes advertised by routing peers. Equivalent to --disable-client-routes. + + Disable server routes + When enabled, this client will not act as a routing peer for other clients. Equivalent to --disable-server-routes. + + Block inbound + When enabled, the client firewall blocks all inbound peer traffic on the WireGuard interface. Equivalent to --block-inbound. + + Allow server SSH + When enabled, this client accepts incoming SSH sessions via NetBird SSH. Equivalent to --allow-server-ssh. + + Enable Rosenpass + Enables Rosenpass post-quantum key exchange on WireGuard tunnels. Both peers must support it. + + Rosenpass permissive + When enabled, the client falls back to plain WireGuard if a peer does not support Rosenpass; otherwise it refuses the connection. + + WireGuard port + UDP port used by the local WireGuard interface. Allowed range: 1-65535. + + Split tunnel + Restrict the NetBird tunnel to or from a chosen list of application package names. Choose either the allow mode (only the listed apps route through NetBird) or the disallow mode (the listed apps bypass NetBird; everything else routes through). The mode is mutually exclusive — only one can be active at a time. Android-only at the daemon level; Windows/macOS/iOS clients ignore this policy. + Allow only listed apps (everything else bypasses) + Disallow listed apps (everything else routes) + + + Disable update settings + When enabled, blocks every configuration change from the client UI and from the CLI (netbird up / login / setconfig). The Settings view stays viewable but read-only. Equivalent to --disable-update-settings. + + Disable profiles + When enabled, the client UI/CLI cannot list, create, switch or remove NetBird connection profiles. Equivalent to --disable-profiles. + + Disable networks + When enabled, the client UI/CLI cannot list, select or deselect NetBird networks (the corresponding daemon RPCs return Unavailable). Equivalent to --disable-networks. + + Disable metrics collection + When enabled, the client does not collect or report local usage metrics. + + + + + + + + https://api.netbird.io:443 + + + + + + + + + + + WireGuard UDP port: + + + + Mode: + + + + + + + + diff --git a/public/docs-static/files/netbird.admx b/public/docs-static/files/netbird.admx new file mode 100644 index 00000000..2f7645d6 --- /dev/null +++ b/public/docs-static/files/netbird.admx @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + allow + disallow + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/client/mdm-integration.mdx b/src/pages/client/mdm-integration.mdx index 7f31d473..7d1976c4 100644 --- a/src/pages/client/mdm-integration.mdx +++ b/src/pages/client/mdm-integration.mdx @@ -131,8 +131,7 @@ same registry key. 6. Verify with `reg query HKLM\Software\Policies\NetBird` — the values you set should appear there. -The ADMX template is shipped in the NetBird repo at -`docs/netbird.admx` / `docs/netbird.adml`. +Download the templates: netbird.admx / netbird.adml. ### Microsoft Intune (ADMX ingestion) @@ -176,12 +175,11 @@ whole policy in a single `.reg` file: reg import netbird-policy.reg ``` -A sample is in the NetBird repo at `docs/netbird-policy.reg`. +Download a sample: netbird-policy.reg. ### JumpCloud -NetBird ships a JumpCloud companion script at -`docs/netbird-policy.reg.ps1`. To use it: +NetBird ships a JumpCloud companion script: netbird-policy.reg.ps1. To use it: 1. In the JumpCloud admin console, go to **Device Management → Commands → +**. @@ -218,8 +216,7 @@ This is the canonical macOS path and works with every MDM (Jamf, Kandji, Mosyle, Microsoft Intune for Mac, Workspace ONE, JumpCloud, Apple Configurator 2, etc.). -1. Start from the template at `docs/netbird-macos.mobileconfig` in the - NetBird repo. Open it in your editor (or in +1. Start from the template netbird-macos.mobileconfig. Open it in your editor (or in [iMazing Profile Editor](https://imazing.com/profile-editor) / [ProfileCreator](https://github.com/ProfileCreator/ProfileCreator)). 2. Inside the `mcx_preference_settings` dictionary, set the keys you @@ -269,7 +266,7 @@ managed-preferences plist: 1. In the JumpCloud admin console, open **Policy Management → Policies → +** and choose the **Mac** platform. 2. Pick the **MDM Custom Configuration Profile** policy template. -3. Upload `docs/io.netbird.client.plist` from the NetBird repository +3. Upload io.netbird.client.plist as the plist payload. Edit the file before upload to enable just the keys you want to enforce — leave the rest commented out. 4. Bind the policy to the target Device Group and save. @@ -303,7 +300,7 @@ which un-locks the corresponding fields on the client. #### Shell Command (no MDM enrollment required) If your fleet is JumpCloud-managed but not MDM-enrolled, NetBird ships -a companion script at `docs/netbird-macos.sh`. It is the macOS +a companion script: netbird-macos.sh. It is the macOS counterpart of the Windows `.reg.ps1` script — same fleet, different backend: From 2aa5f3918efb70bab4444db6001b6418fde48751 Mon Sep 17 00:00:00 2001 From: Riccardo Manfrin <3090891+riccardomanfrin@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:30:05 +0200 Subject: [PATCH 3/4] Update public/docs-static/files/netbird-policy.reg.ps1 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- public/docs-static/files/netbird-policy.reg.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/docs-static/files/netbird-policy.reg.ps1 b/public/docs-static/files/netbird-policy.reg.ps1 index 011d706d..6e67a4b3 100644 --- a/public/docs-static/files/netbird-policy.reg.ps1 +++ b/public/docs-static/files/netbird-policy.reg.ps1 @@ -85,7 +85,11 @@ if ($LASTEXITCODE -ne 0) { # Audit dump so the JumpCloud per-execution log captures the applied state. Write-Host "[netbird-mdm] final policy state under $RegKey :" -& reg.exe query $RegKey /s +if (Test-Path "Registry::$RegKey") { + & reg.exe query $RegKey /s +} else { + Write-Host "[netbird-mdm] no policy values present under $RegKey" +} # Daemon's 1-min reload ticker picks up the change automatically. # Uncomment to force immediate convergence (skips the ticker wait): From ebcdb81e095b92dd92c93c0531b73ae4b8e2e82d Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:32:31 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Fix=20N?= =?UTF-8?q?etBird=20configuration=20and=20deployment=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/docs-static/files/io.netbird.client.plist | 6 +++--- public/docs-static/files/netbird-macos.mobileconfig | 4 ++-- public/docs-static/files/netbird-macos.sh | 8 ++++---- public/docs-static/files/netbird-policy.reg.ps1 | 8 ++------ public/docs-static/files/netbird.admx | 4 ++-- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/public/docs-static/files/io.netbird.client.plist b/public/docs-static/files/io.netbird.client.plist index f42b6b3d..8c231397 100644 --- a/public/docs-static/files/io.netbird.client.plist +++ b/public/docs-static/files/io.netbird.client.plist @@ -19,16 +19,16 @@ For MDM platforms that expect a full Configuration Profile instead of a bare plist (Custom Configuration Profile / .mobileconfig upload), - use docs/netbird-macos.mobileconfig — same keys, additional Payload* + use netbird-macos.mobileconfig — same keys, additional Payload* envelope. Editing this file: - Remove or comment out any key you do NOT want to enforce. The daemon treats an absent key as "no enforcement" for that field. - Keep the document well-formed XML. Validate locally with: - plutil -lint docs/io.netbird.client.plist + plutil -lint io.netbird.client.plist - Keys are camelCase; values are typed (, , , - ). See docs/src/pages/client/mdm-integration.mdx (the + ). See src/pages/client/mdm-integration.mdx (the public docs page) for the full reference. Persistence caveat: diff --git a/public/docs-static/files/netbird-macos.mobileconfig b/public/docs-static/files/netbird-macos.mobileconfig index 53453db5..f1fcd3c3 100644 --- a/public/docs-static/files/netbird-macos.mobileconfig +++ b/public/docs-static/files/netbird-macos.mobileconfig @@ -9,7 +9,7 @@ Read at runtime by the netbird daemon's macOS loader (client/mdm/policy_darwin.go — Phase 2). Key names match the canonical - lowerCamelCase form used in docs/netbird.admx and the mdm.Key* + lowerCamelCase form used in netbird.admx and the mdm.Key* constants in client/mdm/policy.go. Bundle identifier: io.netbird.client @@ -36,7 +36,7 @@ UserDefaults[com.apple.configuration.managed] under a different payload type (com.apple.app.configuration.managed); the wrapper structure is the same but the inner payload dictionary differs. - See docs/netbird-ios.mobileconfig (Phase 5) when shipped. + See netbird-ios.mobileconfig (not yet shipped) when available. --> diff --git a/public/docs-static/files/netbird-macos.sh b/public/docs-static/files/netbird-macos.sh index a2f5ff5e..48933ef1 100644 --- a/public/docs-static/files/netbird-macos.sh +++ b/public/docs-static/files/netbird-macos.sh @@ -4,7 +4,7 @@ # Push the NetBird MDM policy to a macOS device via JumpCloud Commands. # # DESCRIPTION -# This is the macOS counterpart of docs/netbird-policy.reg.ps1. +# This is the macOS counterpart of netbird-policy.reg.ps1. # It writes the values declared in the "POLICY VALUES" block below to # the managed-preferences plist that the NetBird daemon's # client/mdm/policy_darwin.go loader reads on every 1-minute MDM @@ -25,7 +25,7 @@ # IMPORTANT: PERSISTENCE # macOS wipes /Library/Managed Preferences/ at every boot on devices # that are NOT MDM-enrolled. For a persistent fleet rollout, push the -# companion docs/netbird-macos.mobileconfig as a Custom Configuration +# companion netbird-macos.mobileconfig as a Custom Configuration # Profile (Admin Console -> MDM -> Mac Custom Configuration Profiles) # instead of this script. Use this script when: # - the device is MDM-enrolled (file survives reboots), or @@ -49,8 +49,8 @@ set -euo pipefail # # Reference for key names + accepted values: # client/mdm/policy.go (Key* constants) -# docs/netbird-macos.mobileconfig (sample profile) -# docs/netbird.admx + .adml (Windows ADMX schema) +# netbird-macos.mobileconfig (sample profile) +# netbird.admx + netbird.adml (Windows ADMX schema) # NULL='__UNSET__' managementURL='https://api.netbird.io:443' diff --git a/public/docs-static/files/netbird-policy.reg.ps1 b/public/docs-static/files/netbird-policy.reg.ps1 index 6e67a4b3..1d6d6826 100644 --- a/public/docs-static/files/netbird-policy.reg.ps1 +++ b/public/docs-static/files/netbird-policy.reg.ps1 @@ -5,7 +5,7 @@ by importing a sidecar netbird-policy.reg file. .DESCRIPTION - Windows counterpart of docs/netbird-macos.sh. Outcome: + Windows counterpart of netbird-macos.sh. Outcome: HKLM\Software\Policies\NetBird populated from the attached netbird-policy.reg file, daemon picks up the change via the 1-minute MDM reload ticker. @@ -85,11 +85,7 @@ if ($LASTEXITCODE -ne 0) { # Audit dump so the JumpCloud per-execution log captures the applied state. Write-Host "[netbird-mdm] final policy state under $RegKey :" -if (Test-Path "Registry::$RegKey") { - & reg.exe query $RegKey /s -} else { - Write-Host "[netbird-mdm] no policy values present under $RegKey" -} +& reg.exe query $RegKey /s # Daemon's 1-min reload ticker picks up the change automatically. # Uncomment to force immediate convergence (skips the ticker wait): diff --git a/public/docs-static/files/netbird.admx b/public/docs-static/files/netbird.admx index 2f7645d6..51aeccf6 100644 --- a/public/docs-static/files/netbird.admx +++ b/public/docs-static/files/netbird.admx @@ -163,11 +163,11 @@ allow disallow - + - +