fix allowserverssh mdm

This commit is contained in:
Eduard Gert
2026-06-15 13:54:07 +02:00
parent 47efdd839c
commit 83c9e5b0d1
5 changed files with 11 additions and 12 deletions

View File

@@ -106,7 +106,7 @@ State that crosses screens/windows lives in context, each provider mounted exact
- **`useStatus`** (`StatusContext`) — `{ status, error, refresh, isReady, isDaemonAvailable, isDaemonUnavailable }`. Owns the single `DaemonFeed.Get` + `netbird:status` subscription and the daemon gate (see Layouts). `refresh()` after Connect/Disconnect to dodge a few hundred ms of event-stream lag.
- **`ProfileContext`** — `username`, `activeProfile`, `profiles`, plus `refresh` / `switchProfile` / `addProfile` / `removeProfile` / `logoutProfile`. `switchProfile` delegates to `ProfileSwitcher.SwitchActive` (the Go-side single source of truth — drives the optimistic-Connecting paint and `Peers` suppression). The other methods are thin wrappers over `Profiles.*` / `Connection.Logout` + a `refresh()`.
- **`SettingsContext`** — `setField` / `saveField` / `saveFields` / `saveNow` over `Settings.GetConfig|SetConfig` with 400ms debounce. Renders `<SettingsSkeleton/>` while `config === null`. **PSK mask quirk:** `GetConfig` returns existing PSKs as `"**********"`; sending the mask back round-trips it into storage and `wgtypes.ParseKey` fails on the next connect — `save` drops the field when it equals the mask.
- **`MdmContext`** — `useMdm()` returns `config.managedFields` as `Record<string, boolean>`, **keyed by the daemon's `mdm.Key*` names exactly as written in the policy source** (`managementURL`, `allowServerSSH`, `preSharedKey`, `wireguardPort`, `rosenpassEnabled`/`Permissive`, `disableClientRoutes`/`disableServerRoutes`, `disableAutoConnect`, `blockInbound`). No GUI-side renaming — what the Group Policy admin writes is what the lookup key is. Mounted in `AppLayout` (under `ProfileProvider`); fetches `Settings.GetConfig` once, re-fetches on the daemon's `netbird:event` `metadata.type=config_changed` push so policy flips paint live. No second copy of the locked *values* — MDM is a global override, so the active profile's resolved `useSettings().config.<field>` already carries the MDM-enforced value. Consumers: Settings tabs hide individual toggles/sections (both rosenpass keys managed ⇒ whole encryption section hidden); `SettingsNavigation` + `SettingsPage` hide the SSH tab when `managed.allowServerSSH` is set and bounce `active="ssh"` back to General; `ProfileCreationModal` skips the Cloud/self-hosted picker when `managed.managementURL` is set and submits the resolved URL verbatim; `WelcomeDialog` reads `config.managedFields.managementURL` directly (sits outside `AppLayout`) to skip the management step on a fresh install.
- **`MdmContext`** — `useMdm()` returns `config.managedFields` as `Record<string, boolean>`, **keyed by the daemon's `mdm.Key*` names exactly as written in the policy source** (`managementURL`, `allowServerSSH`, `preSharedKey`, `wireguardPort`, `rosenpassEnabled`/`Permissive`, `disableClientRoutes`/`disableServerRoutes`, `disableAutoConnect`, `blockInbound`). No GUI-side renaming — what the Group Policy admin writes is what the lookup key is. Mounted in `AppLayout` (under `ProfileProvider`); fetches `Settings.GetConfig` once, re-fetches on the daemon's `netbird:event` `metadata.type=config_changed` push so policy flips paint live. No second copy of the locked *values* — MDM is a global override, so the active profile's resolved `useSettings().config.<field>` already carries the MDM-enforced value. Consumers: Settings tabs hide individual toggles/sections (both rosenpass keys managed ⇒ whole encryption section hidden); `mdm.*` fields are **is-managed flags**`true` when the field name appears in `MDMManagedFields`, `false` otherwise. Consumers check truthiness (`!mdm.x` to mean "not managed"). The *resolved value* of a managed field is already enforced into `Config`, so consumers read it via `useSettings().config.<field>` and hide the toggle when `mdm.<field>` is true. **Three carve-outs** that carry the resolved value instead, populated outside the reflective is-managed loop in `services/settings.go` `GetRestrictions`: `mdm.managementURL` (string — used by `ProfileCreationModal` / `WelcomeDialog`, which need the URL to render the form); `mdm.allowServerSSH` (`*bool` / `boolean | null` — used by `SettingsNavigation` + `SettingsPage` to gate the SSH tab on `mdm.allowServerSSH ?? !features.disableUpdateSettings`; tri-state needed because the tab gate lives outside `SettingsProvider` and can't read `config.serverSshAllowed`, and MDM-managed-with-value-`false` must hide the tab — falsy fallthrough would leak it); and `mdm.disableAdvancedView` (`bool` — MDM-only on the daemon, no CLI fallback; plumbed via the `GetFeatures` RPC rather than `MDMManagedFields`, so set directly from `featResp.GetDisableAdvancedView()`. Value semantic: `true` iff MDM explicitly disables; the nil/false collapse is fine because both mean "advanced view available"). The three `features.*` gates (`disableProfiles`/`Networks`/`UpdateSettings`) live in `Features` because they accept a CLI-flag fallback in addition to MDM (`--disable-profiles` etc.) — `disableAdvancedView` doesn't, which is why it sits with the MDM-only carve-outs. `ProfileCreationModal` skips the Cloud/self-hosted picker when `managed.managementURL` is set and submits the resolved URL verbatim; `WelcomeDialog` reads `config.managedFields.managementURL` directly (sits outside `AppLayout`) to skip the management step on a fresh install.
- **`DebugBundleContext`** — stages `idle → preparing-trace → reconnecting → capturing → restoring-level → bundling → uploading → done`. Cancellable via `AbortController` at any stage; cancel restores the original log level best-effort. Upload URL is the hardcoded `NETBIRD_UPLOAD_URL`.
- **`ClientVersionContext`** — seeds from `Update.GetState()`, subscribes to `netbird:update:state`; exposes `{ updateAvailable, updateVersion, enforced, installing, triggerUpdate, updating }`. Three branches:
1. `available && !enforced` — download-only; `UpdateVersionCard` → opens GitHub releases.

View File

@@ -34,7 +34,7 @@ const MainBody = () => {
const { viewMode, setViewMode } = useViewMode();
const { mdm, features } = useRestrictions();
// Force flip the view if mdm changed it
// Force flip the view if MDM disabled advanced
useEffect(() => {
if (mdm.disableAdvancedView && viewMode === "advanced") {
setViewMode("default");

View File

@@ -19,7 +19,7 @@ export const SettingsNavigation = () => {
const { t } = useTranslation();
const { updateAvailable } = useClientVersion();
const { mdm, features } = useRestrictions();
const showSsh = mdm.allowServerSSH || !features.disableUpdateSettings;
const showSsh = mdm.allowServerSSH ?? !features.disableUpdateSettings;
const aboutAdornment = updateAvailable ? (
<Tooltip content={t("settings.tabs.updateAvailable")} side={"right"}>

View File

@@ -54,7 +54,7 @@ export const SettingsPage = () => {
[Tab.Network]: editable,
[Tab.Security]: editable,
[Tab.Profiles]: !features.disableProfiles,
[Tab.SSH]: mdm.allowServerSSH || editable,
[Tab.SSH]: mdm.allowServerSSH ?? editable,
[Tab.Advanced]: editable,
[Tab.Troubleshooting]: true,
[Tab.About]: true,

View File

@@ -18,13 +18,13 @@ type MDMFields struct {
RosenpassPermissive bool `json:"rosenpassPermissive"`
DisableClientRoutes bool `json:"disableClientRoutes"`
DisableServerRoutes bool `json:"disableServerRoutes"`
AllowServerSSH bool `json:"allowServerSSH"`
AllowServerSSH *bool `json:"allowServerSSH"`
DisableAutoConnect bool `json:"disableAutoConnect"`
BlockInbound bool `json:"blockInbound"`
DisableMetricsCollection bool `json:"disableMetricsCollection"`
SplitTunnelMode bool `json:"splitTunnelMode"`
SplitTunnelApps bool `json:"splitTunnelApps"`
DisableAdvancedView bool `json:"disableAdvancedView"`
DisableAdvancedView bool `json:"disableAdvancedView"`
}
type Features struct {
@@ -222,6 +222,9 @@ func (s *Settings) GetRestrictions(ctx context.Context) (Restrictions, error) {
DisableNetworks: featResp.GetDisableNetworks(),
DisableUpdateSettings: featResp.GetDisableUpdateSettings(),
},
MDM: MDMFields{
DisableAdvancedView: featResp.GetDisableAdvancedView(),
},
}
managed := cfgResp.GetMDMManagedFields()
if len(managed) > 0 {
@@ -235,10 +238,6 @@ func (s *Settings) GetRestrictions(ctx context.Context) (Restrictions, error) {
if v.Field(i).Kind() != reflect.Bool {
continue
}
// AllowServerSSH carries the resolved value (like ManagementURL), not the is-managed flag.
if t.Field(i).Name == "AllowServerSSH" {
continue
}
if _, ok := set[t.Field(i).Tag.Get("json")]; ok {
v.Field(i).SetBool(true)
}
@@ -247,9 +246,9 @@ func (s *Settings) GetRestrictions(ctx context.Context) (Restrictions, error) {
r.MDM.ManagementURL = cfgResp.GetManagementUrl()
}
if _, ok := set["allowServerSSH"]; ok {
r.MDM.AllowServerSSH = cfgResp.GetServerSSHAllowed()
allowed := cfgResp.GetServerSSHAllowed()
r.MDM.AllowServerSSH = &allowed
}
}
r.MDM.DisableAdvancedView = featResp.GetDisableAdvancedView()
return r, nil
}