From 6d9cb9008ae3882057eda22b6d842d0c4e1b9cf1 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Tue, 7 Jul 2026 19:23:20 +0200 Subject: [PATCH] Gate local metrics settings behind update-settings and MDM policy --- client/internal/profilemanager/config.go | 6 +++++ .../profilemanager/config_mdm_test.go | 26 +++++++++++++++++++ client/mdm/policy.go | 10 ++++--- client/server/mdm.go | 12 +++++++-- client/server/setconfig_mdm_test.go | 24 +++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/client/internal/profilemanager/config.go b/client/internal/profilemanager/config.go index 8973f1a64..5a35dd247 100644 --- a/client/internal/profilemanager/config.go +++ b/client/internal/profilemanager/config.go @@ -730,6 +730,12 @@ func (config *Config) applyMDMPolicy(policy *mdm.Policy) { applyBool(mdm.KeyDisableAutoConnect, func(v bool) { config.DisableAutoConnect = v }) applyBool(mdm.KeyRosenpassEnabled, func(v bool) { config.RosenpassEnabled = v }) applyBool(mdm.KeyRosenpassPermissive, func(v bool) { config.RosenpassPermissive = v }) + applyBool(mdm.KeyEnableLocalMetrics, func(v bool) { config.LocalMetricsEnabled = v }) + + if v, ok := policy.GetString(mdm.KeyLocalMetricsAddress); ok { + config.LocalMetricsAddress = v + logApplied(mdm.KeyLocalMetricsAddress, v) + } if v, ok := policy.GetInt(mdm.KeyWireguardPort); ok { // REG_DWORD is 32-bit; UDP port range is 1-65535. Clamp at the diff --git a/client/internal/profilemanager/config_mdm_test.go b/client/internal/profilemanager/config_mdm_test.go index c6a688ab2..f8dfddb33 100644 --- a/client/internal/profilemanager/config_mdm_test.go +++ b/client/internal/profilemanager/config_mdm_test.go @@ -130,6 +130,32 @@ func TestApply_MDMBoolKeysOverrideOnDiskValue(t *testing.T) { assert.True(t, cfg.Policy().HasKey(mdm.KeyRosenpassEnabled)) } +func TestApply_MDMLocalMetrics(t *testing.T) { + tmp := filepath.Join(t.TempDir(), "config.json") + + // Seed without MDM. + withMDMPolicy(t, mdm.NewPolicy(nil)) + _, err := UpdateOrCreateConfig(ConfigInput{ + ConfigPath: tmp, + LocalMetricsEnabled: boolPtr(false), + }) + require.NoError(t, err) + + withMDMPolicy(t, mdm.NewPolicy(map[string]any{ + mdm.KeyEnableLocalMetrics: true, + mdm.KeyLocalMetricsAddress: "127.0.0.1:9292", + })) + + cfg, err := UpdateOrCreateConfig(ConfigInput{ConfigPath: tmp}) + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.True(t, cfg.LocalMetricsEnabled, "MDM override should flip on-disk false to true") + assert.Equal(t, "127.0.0.1:9292", cfg.LocalMetricsAddress) + assert.True(t, cfg.Policy().HasKey(mdm.KeyEnableLocalMetrics)) + assert.True(t, cfg.Policy().HasKey(mdm.KeyLocalMetricsAddress)) +} + func TestApply_MDMLazyConnection(t *testing.T) { cases := []struct { name string diff --git a/client/mdm/policy.go b/client/mdm/policy.go index b76c70a75..99e32d74a 100644 --- a/client/mdm/policy.go +++ b/client/mdm/policy.go @@ -20,10 +20,10 @@ import ( // names (lowerCamelCase) so the daemon can map a Policy key directly to a // configuration field. const ( - KeyManagementURL = "managementURL" - KeyDisableUpdateSettings = "disableUpdateSettings" - KeyDisableProfiles = "disableProfiles" - KeyDisableNetworks = "disableNetworks" + KeyManagementURL = "managementURL" + KeyDisableUpdateSettings = "disableUpdateSettings" + KeyDisableProfiles = "disableProfiles" + KeyDisableNetworks = "disableNetworks" // KeyDisableAdvancedView gates the advanced-view section in the // upcoming UI revision. UI-only: NOT stored on Config, not // applied by applyMDMPolicy, not rejectable via SetConfig. The @@ -41,6 +41,8 @@ const ( KeyRosenpassEnabled = "rosenpassEnabled" KeyRosenpassPermissive = "rosenpassPermissive" KeyWireguardPort = "wireguardPort" + KeyEnableLocalMetrics = "enableLocalMetrics" + KeyLocalMetricsAddress = "localMetricsAddress" // Split tunnel is modeled as a single conceptual policy with two // registry/plist values. KeySplitTunnelMode is the discriminator diff --git a/client/server/mdm.go b/client/server/mdm.go index 9836c6bea..f8e549c1b 100644 --- a/client/server/mdm.go +++ b/client/server/mdm.go @@ -301,6 +301,8 @@ func mdmManagedFieldConflicts(msg *proto.SetConfigRequest, policy *mdm.Policy) [ conflictBool(mdm.KeyDisableServerRoutes, msg.DisableServerRoutes), conflictBool(mdm.KeyBlockInbound, msg.BlockInbound), conflictInt64(mdm.KeyWireguardPort, msg.WireguardPort), + conflictBool(mdm.KeyEnableLocalMetrics, msg.EnableLocalMetrics), + conflictString(mdm.KeyLocalMetricsAddress, msg.GetLocalMetricsAddress()), }) } @@ -346,7 +348,9 @@ func setConfigRequestHasConfigOverrides(msg *proto.SetConfigRequest) bool { msg.EnableSSHLocalPortForwarding != nil || msg.EnableSSHRemotePortForwarding != nil || msg.DisableSSHAuth != nil || - msg.SshJWTCacheTTL != nil + msg.SshJWTCacheTTL != nil || + msg.EnableLocalMetrics != nil || + msg.LocalMetricsAddress != nil } // loginRequestHasConfigOverrides reports whether the LoginRequest @@ -381,7 +385,9 @@ func loginRequestHasConfigOverrides(msg *proto.LoginRequest) bool { msg.BlockLanAccess != nil || msg.DisableNotifications != nil || len(msg.DnsLabels) > 0 || msg.CleanDNSLabels || - msg.BlockInbound != nil + msg.BlockInbound != nil || + msg.EnableLocalMetrics != nil || + msg.LocalMetricsAddress != nil } // loginRequestMDMConflicts mirrors mdmManagedFieldConflicts but for the @@ -422,6 +428,8 @@ func loginRequestMDMConflicts(msg *proto.LoginRequest, policy *mdm.Policy) []str conflictBool(mdm.KeyDisableServerRoutes, msg.DisableServerRoutes), conflictBool(mdm.KeyBlockInbound, msg.BlockInbound), conflictInt64(mdm.KeyWireguardPort, msg.WireguardPort), + conflictBool(mdm.KeyEnableLocalMetrics, msg.EnableLocalMetrics), + conflictString(mdm.KeyLocalMetricsAddress, msg.GetLocalMetricsAddress()), }) } diff --git a/client/server/setconfig_mdm_test.go b/client/server/setconfig_mdm_test.go index 9baf16136..4803548ae 100644 --- a/client/server/setconfig_mdm_test.go +++ b/client/server/setconfig_mdm_test.go @@ -132,6 +132,30 @@ func TestSetConfig_MDMReject_MultipleFields(t *testing.T) { }, v.GetFields()) } +func TestSetConfig_MDMReject_LocalMetrics(t *testing.T) { + withMDMPolicy(t, mdm.NewPolicy(map[string]any{ + mdm.KeyEnableLocalMetrics: true, + mdm.KeyLocalMetricsAddress: "127.0.0.1:9191", + })) + + s, ctx, profName, username, _ := setupServerWithProfile(t) + + enabled := false + addr := "0.0.0.0:9999" + _, err := s.SetConfig(ctx, &proto.SetConfigRequest{ + ProfileName: profName, + Username: username, + EnableLocalMetrics: &enabled, + LocalMetricsAddress: &addr, + }) + + v := extractViolation(t, err) + assert.ElementsMatch(t, []string{ + mdm.KeyEnableLocalMetrics, + mdm.KeyLocalMetricsAddress, + }, v.GetFields()) +} + func TestSetConfig_MDMReject_AllOrNothing(t *testing.T) { // MDM enforces ManagementURL only; user request touches both the // enforced field AND a non-enforced field (RosenpassEnabled).