Merge main into ui-refactor; port MDM support to the Wails UI

Integrates main's MDM configuration-profile feature and adapts it to the
Wails UI (this branch had already replaced the Fyne UI).

Conflict resolution:
- go.mod/go.sum: take main's deps; howett.net/plist pinned to v1.0.2-... (tidy)
- client/proto/daemon.pb.go: regenerated from the merged daemon.proto
- client/internal/peer/status.go: union of ipToKey (main) + sessionExpiresAt (HEAD)
- client/server/server.go: main's intent/liveness model (connectionGoroutineRunning,
  clientRunning no longer cleared by the goroutine) + empty-PSK guard
- client/ui/client_ui.go, client/ui/profile.go: removed (dead Fyne UI)

MDM port (backend + tray):
- services/settings.go: expose MDMManagedFields plus a managedFields map keyed
  by Config field names so the settings form can gate a control without
  translating mdm.Key* names
- tray: gate Profiles / Exit Node menus on DisableProfiles / DisableNetworks via
  GetFeatures, refreshed on the config_changed system event (replaces the legacy
  2s poll); localized MDM policy-applied toast in all shipped locales
- client/proto/metadata.go: shared constants for the config_changed /
  policy_applied event markers

PreSharedKey: GetConfig now returns preSharedKeySet (bool) instead of the masked
value; the settings form provides its own placeholder and sends a new key only
when the user types one.
This commit is contained in:
Zoltan Papp
2026-06-12 15:27:23 +02:00
56 changed files with 3639 additions and 314 deletions

View File

@@ -5,9 +5,28 @@ package services
import (
"context"
"github.com/netbirdio/netbird/client/mdm"
"github.com/netbirdio/netbird/client/proto"
)
// mdmKeyToConfigField maps an MDM policy key (mdm.Key*) to the JSON field name
// of the matching Config field, so GetConfig can translate the daemon's key
// names to the frontend's field names in exactly one place. Mirrors the
// conflict set the daemon enforces on SetConfig/Login (mdmManagedFieldConflicts);
// keys with no settings field are absent.
var mdmKeyToConfigField = map[string]string{
mdm.KeyManagementURL: "managementUrl",
mdm.KeyPreSharedKey: "preSharedKey",
mdm.KeyWireguardPort: "wireguardPort",
mdm.KeyRosenpassEnabled: "rosenpassEnabled",
mdm.KeyRosenpassPermissive: "rosenpassPermissive",
mdm.KeyDisableClientRoutes: "disableClientRoutes",
mdm.KeyDisableServerRoutes: "disableServerRoutes",
mdm.KeyAllowServerSSH: "serverSshAllowed",
mdm.KeyDisableAutoConnect: "disableAutoConnect",
mdm.KeyBlockInbound: "blockInbound",
}
// ConfigParams selects which profile/user to read or write config for.
type ConfigParams struct {
ProfileName string `json:"profileName"`
@@ -18,33 +37,51 @@ type ConfigParams struct {
// Pointer fields mark "set" vs "unset" so the UI can omit a value to keep the
// daemon's current setting (matching SetConfigRequest's optional semantics).
type Config struct {
ManagementURL string `json:"managementUrl"`
AdminURL string `json:"adminUrl"`
ConfigFile string `json:"configFile"`
LogFile string `json:"logFile"`
PreSharedKey string `json:"preSharedKey"`
InterfaceName string `json:"interfaceName"`
WireguardPort int64 `json:"wireguardPort"`
MTU int64 `json:"mtu"`
DisableAutoConnect bool `json:"disableAutoConnect"`
ServerSSHAllowed bool `json:"serverSshAllowed"`
RosenpassEnabled bool `json:"rosenpassEnabled"`
RosenpassPermissive bool `json:"rosenpassPermissive"`
DisableNotifications bool `json:"disableNotifications"`
LazyConnectionEnabled bool `json:"lazyConnectionEnabled"`
BlockInbound bool `json:"blockInbound"`
NetworkMonitor bool `json:"networkMonitor"`
DisableClientRoutes bool `json:"disableClientRoutes"`
DisableServerRoutes bool `json:"disableServerRoutes"`
DisableDNS bool `json:"disableDns"`
DisableIPv6 bool `json:"disableIpv6"`
BlockLANAccess bool `json:"blockLanAccess"`
EnableSSHRoot bool `json:"enableSshRoot"`
EnableSSHSFTP bool `json:"enableSshSftp"`
EnableSSHLocalPortForwarding bool `json:"enableSshLocalPortForwarding"`
EnableSSHRemotePortForwarding bool `json:"enableSshRemotePortForwarding"`
DisableSSHAuth bool `json:"disableSshAuth"`
SSHJWTCacheTTL int32 `json:"sshJwtCacheTtl"`
ManagementURL string `json:"managementUrl"`
AdminURL string `json:"adminUrl"`
ConfigFile string `json:"configFile"`
LogFile string `json:"logFile"`
// PreSharedKeySet reports whether a pre-shared key is configured, without
// exposing its value (the daemon redacts the PSK). The settings form shows
// its own "configured" / "managed by MDM" placeholder when true and sends a
// new PSK only when the user actually types one — the redaction sentinel
// never crosses to the UI.
PreSharedKeySet bool `json:"preSharedKeySet"`
InterfaceName string `json:"interfaceName"`
WireguardPort int64 `json:"wireguardPort"`
MTU int64 `json:"mtu"`
DisableAutoConnect bool `json:"disableAutoConnect"`
ServerSSHAllowed bool `json:"serverSshAllowed"`
RosenpassEnabled bool `json:"rosenpassEnabled"`
RosenpassPermissive bool `json:"rosenpassPermissive"`
DisableNotifications bool `json:"disableNotifications"`
LazyConnectionEnabled bool `json:"lazyConnectionEnabled"`
BlockInbound bool `json:"blockInbound"`
NetworkMonitor bool `json:"networkMonitor"`
DisableClientRoutes bool `json:"disableClientRoutes"`
DisableServerRoutes bool `json:"disableServerRoutes"`
DisableDNS bool `json:"disableDns"`
DisableIPv6 bool `json:"disableIpv6"`
BlockLANAccess bool `json:"blockLanAccess"`
EnableSSHRoot bool `json:"enableSshRoot"`
EnableSSHSFTP bool `json:"enableSshSftp"`
EnableSSHLocalPortForwarding bool `json:"enableSshLocalPortForwarding"`
EnableSSHRemotePortForwarding bool `json:"enableSshRemotePortForwarding"`
DisableSSHAuth bool `json:"disableSshAuth"`
SSHJWTCacheTTL int32 `json:"sshJwtCacheTtl"`
// MDMManagedFields is the raw list of MDM-managed policy keys exactly as
// the daemon reports them (mdm.Key* names, e.g. "managementURL",
// "preSharedKey", "splitTunnelMode"). Includes keys with no settings
// field (split-tunnel, metrics, the Disable* feature flags). The faithful
// full set; prefer ManagedFields for per-field gating.
MDMManagedFields []string `json:"mdmManagedFields"`
// ManagedFields is the MDM-managed set normalised to Config JSON field
// names (e.g. "managementUrl", "serverSshAllowed", "preSharedKey"), so the
// settings form can gate a control with managedFields[fieldName] without
// translating the daemon's mdm.Key* names. Only managed fields are present
// (value true); keys with no settings field are omitted (the Disable*
// feature flags come via GetFeatures instead).
ManagedFields map[string]bool `json:"managedFields"`
}
// SetConfigParams is a partial update — only fields with non-nil pointers
@@ -114,7 +151,7 @@ func (s *Settings) GetConfig(ctx context.Context, p ConfigParams) (Config, error
AdminURL: resp.GetAdminURL(),
ConfigFile: resp.GetConfigFile(),
LogFile: resp.GetLogFile(),
PreSharedKey: resp.GetPreSharedKey(),
PreSharedKeySet: resp.GetPreSharedKey() != "",
InterfaceName: resp.GetInterfaceName(),
WireguardPort: resp.GetWireguardPort(),
MTU: resp.GetMtu(),
@@ -137,6 +174,8 @@ func (s *Settings) GetConfig(ctx context.Context, p ConfigParams) (Config, error
EnableSSHRemotePortForwarding: resp.GetEnableSSHRemotePortForwarding(),
DisableSSHAuth: resp.GetDisableSSHAuth(),
SSHJWTCacheTTL: resp.GetSshJWTCacheTTL(),
MDMManagedFields: resp.GetMDMManagedFields(),
ManagedFields: configManagedFields(resp.GetMDMManagedFields()),
}, nil
}
@@ -194,3 +233,17 @@ func (s *Settings) GetFeatures(ctx context.Context) (Features, error) {
DisableNetworks: resp.GetDisableNetworks(),
}, nil
}
// configManagedFields normalises the daemon's MDM-managed key list (mdm.Key*
// names) to a set keyed by Config JSON field names, so the settings form can
// look up a field's locked state directly. Returns a non-nil (possibly empty)
// map so it marshals to {} rather than null.
func configManagedFields(managed []string) map[string]bool {
out := make(map[string]bool, len(managed))
for _, k := range managed {
if field, ok := mdmKeyToConfigField[k]; ok {
out[field] = true
}
}
return out
}