docs: iOS enforces every key; document when a change reaches the tunnel

The iOS section was written while the policy stopped at the app: the
network extension that runs the engine has its own preferences domain,
and Apple's app-configuration channel does not deliver there. The app
now mirrors the configuration into the shared App Group and tells the
extension when it changes, so the keys the engine applies to the
connection are enforced too.

What replaces the old warning is a timing caveat, which is what an admin
actually needs in order to plan a rollout. The hand-off happens only
while the app is running: in the foreground a change reaches the tunnel
within about 30 seconds or immediately on activation, but iOS suspends a
backgrounded app, so a policy pushed — or withdrawn — in that window
applies the next time the app is opened. A tunnel brought up by VPN On
Demand or the widget therefore runs on the last policy the app saw.

tvOS keeps the old behaviour and now says so: the hand-off needs a shared
app group, which does not work between the tvOS app and its extension, so
only the interface is enforced there.

Also corrected: blockInbound, wireguardPort and lazyConnection are no
longer listed as having no effect but as having no control to lock, and
debugBundleUploadURL moves out of the ignored list, since the engine
applies it when a remote job produces a bundle. The app-config template
carries the same three keys and the same timing note.
This commit is contained in:
riccardom
2026-09-17 09:17:25 +02:00
parent 182b1b9e87
commit 54aa32788e
2 changed files with 101 additions and 38 deletions
@@ -25,23 +25,26 @@
- Validate locally with:
plutil -lint netbird-ios-appconfig.plist
Scope on iOS: these keys lock and reshape the app's interface, and
the app refuses configuration changes accordingly. Keys the tunnel
engine applies to the connection itself are not yet enforced on
iOS, because the engine runs in a Network Extension that does not
receive the app configuration. See the iOS section of
docs/src/pages/client/mdm-integration.mdx.
When a change takes effect: iOS hands this configuration to the
NetBird app, and the app passes it to the network extension that
runs the tunnel whenever the app is running. With the app in the
foreground a change reaches the tunnel within about 30 seconds;
while the app is closed or backgrounded iOS suspends it, so a
policy pushed (or withdrawn) in that window applies the next time
the app is opened. A tunnel brought up by VPN On Demand or the
widget runs on the last policy the app saw.
Keys with no iOS surface (allowServerSSH, disableServerRoutes,
disableAutostart, disableMetricsCollection, enableLocalMetrics,
localMetricsAddress, debugBundleUploadURL, splitTunnelMode,
splitTunnelApps) are parsed and ignored here, so one payload can be
shared with desktop devices.
tvOS: only the interface is enforced. The hand-off needs a shared
app group, which does not work between the tvOS app and its
extension, so the tvOS engine runs without the policy.
blockInbound, wireguardPort and lazyConnection are left out of this
template on purpose: they are applied by the tunnel engine, which
on iOS does not receive this configuration yet. They do no harm in
a shared payload, they simply have no effect on an iOS device.
Keys that do not apply to the platform are parsed and ignored here,
so one payload can be shared with desktop devices: allowServerSSH,
disableServerRoutes, disableAutostart, disableMetricsCollection,
enableLocalMetrics, localMetricsAddress, splitTunnelMode,
splitTunnelApps.
See the iOS section of docs/src/pages/client/mdm-integration.mdx.
-->
<plist version="1.0">
<dict>
@@ -99,11 +102,29 @@
-->
<!-- ===== Remote jobs =====
Present at any value locks the client toggle, so pushing
false pins remote jobs off. -->
allowRemoteJobs present at any value locks the client toggle,
so pushing false pins remote jobs off. debugBundleUploadURL
overrides where a bundle produced by a remote job is sent. -->
<!--
<key>allowRemoteJobs</key>
<false/>
<key>debugBundleUploadURL</key>
<string>https://uploads.example.com</string>
-->
<!-- ===== Applied by the tunnel engine =====
No control to lock in the app - the engine applies these to
the connection itself. -->
<!--
<key>blockInbound</key>
<false/>
<key>wireguardPort</key>
<integer>51820</integer>
<key>lazyConnection</key>
<true/>
-->
</dict>