summaryrefslogtreecommitdiffhomepage
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/syspolicy/handler.go9
-rw-r--r--util/syspolicy/internal/internal.go1
-rw-r--r--util/syspolicy/internal/metrics/metrics.go7
-rw-r--r--util/syspolicy/internal/metrics/metrics_test.go3
-rw-r--r--util/syspolicy/pkey/pkey.go133
-rw-r--r--util/syspolicy/policy_keys.go199
-rw-r--r--util/syspolicy/policy_keys_test.go3
-rw-r--r--util/syspolicy/policyclient/policyclient.go51
-rw-r--r--util/syspolicy/rsop/change_callbacks.go6
-rw-r--r--util/syspolicy/rsop/resultant_policy_test.go76
-rw-r--r--util/syspolicy/setting/key.go13
-rw-r--r--util/syspolicy/setting/raw_item.go3
-rw-r--r--util/syspolicy/setting/setting.go13
-rw-r--r--util/syspolicy/setting/setting_test.go3
-rw-r--r--util/syspolicy/setting/snapshot.go25
-rw-r--r--util/syspolicy/source/env_policy_store.go15
-rw-r--r--util/syspolicy/source/env_policy_store_test.go5
-rw-r--r--util/syspolicy/source/policy_reader.go5
-rw-r--r--util/syspolicy/source/policy_reader_test.go9
-rw-r--r--util/syspolicy/source/policy_source.go9
-rw-r--r--util/syspolicy/source/policy_store_windows.go17
-rw-r--r--util/syspolicy/source/policy_store_windows_test.go7
-rw-r--r--util/syspolicy/source/test_store.go69
-rw-r--r--util/syspolicy/syspolicy.go18
-rw-r--r--util/syspolicy/syspolicy_test.go63
25 files changed, 420 insertions, 342 deletions
diff --git a/util/syspolicy/handler.go b/util/syspolicy/handler.go
index f511f0a56..555fe9fc3 100644
--- a/util/syspolicy/handler.go
+++ b/util/syspolicy/handler.go
@@ -5,6 +5,7 @@ package syspolicy
import (
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/rsop"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/syspolicy/source"
@@ -89,22 +90,22 @@ func (s handlerStore) RegisterChangeCallback(callback func()) (unregister func()
}
// ReadString implements [source.Store].
-func (s handlerStore) ReadString(key setting.Key) (string, error) {
+func (s handlerStore) ReadString(key pkey.Key) (string, error) {
return s.h.ReadString(string(key))
}
// ReadUInt64 implements [source.Store].
-func (s handlerStore) ReadUInt64(key setting.Key) (uint64, error) {
+func (s handlerStore) ReadUInt64(key pkey.Key) (uint64, error) {
return s.h.ReadUInt64(string(key))
}
// ReadBoolean implements [source.Store].
-func (s handlerStore) ReadBoolean(key setting.Key) (bool, error) {
+func (s handlerStore) ReadBoolean(key pkey.Key) (bool, error) {
return s.h.ReadBoolean(string(key))
}
// ReadStringArray implements [source.Store].
-func (s handlerStore) ReadStringArray(key setting.Key) ([]string, error) {
+func (s handlerStore) ReadStringArray(key pkey.Key) ([]string, error) {
return s.h.ReadStringArray(string(key))
}
diff --git a/util/syspolicy/internal/internal.go b/util/syspolicy/internal/internal.go
index 8f2889625..876ab9599 100644
--- a/util/syspolicy/internal/internal.go
+++ b/util/syspolicy/internal/internal.go
@@ -35,6 +35,7 @@ type TB interface {
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
+ Skip(...any)
}
// EqualJSONForTest compares the JSON in j1 and j2 for semantic equality.
diff --git a/util/syspolicy/internal/metrics/metrics.go b/util/syspolicy/internal/metrics/metrics.go
index 0a2aa1192..3c00e3369 100644
--- a/util/syspolicy/internal/metrics/metrics.go
+++ b/util/syspolicy/internal/metrics/metrics.go
@@ -17,6 +17,7 @@ import (
"tailscale.com/util/slicesx"
"tailscale.com/util/syspolicy/internal"
"tailscale.com/util/syspolicy/internal/loggerx"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/testenv"
)
@@ -209,7 +210,7 @@ func scopeMetrics(origin *setting.Origin) *policyScopeMetrics {
var (
settingMetricsMu sync.RWMutex
- settingMetricsMap map[setting.Key]*settingMetrics
+ settingMetricsMap map[pkey.Key]*settingMetrics
)
func settingMetricsFor(setting *setting.Definition) *settingMetrics {
@@ -283,8 +284,8 @@ func SetHooksForTest(tb internal.TB, addMetric, setMetric metricFn) {
lazyUserMetrics.SetForTest(tb, newScopeMetrics(setting.UserSetting), nil)
}
-func newSettingMetric(key setting.Key, scope setting.Scope, suffix string, typ clientmetric.Type) metric {
- name := strings.ReplaceAll(string(key), string(setting.KeyPathSeparator), "_")
+func newSettingMetric(key pkey.Key, scope setting.Scope, suffix string, typ clientmetric.Type) metric {
+ name := strings.ReplaceAll(string(key), string(pkey.KeyPathSeparator), "_")
return newMetric([]string{name, metricScopeName(scope), suffix}, typ)
}
diff --git a/util/syspolicy/internal/metrics/metrics_test.go b/util/syspolicy/internal/metrics/metrics_test.go
index 07be4773c..a99938769 100644
--- a/util/syspolicy/internal/metrics/metrics_test.go
+++ b/util/syspolicy/internal/metrics/metrics_test.go
@@ -10,13 +10,14 @@ import (
"tailscale.com/types/lazy"
"tailscale.com/util/clientmetric"
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
func TestSettingMetricNames(t *testing.T) {
tests := []struct {
name string
- key setting.Key
+ key pkey.Key
scope setting.Scope
suffix string
typ clientmetric.Type
diff --git a/util/syspolicy/pkey/pkey.go b/util/syspolicy/pkey/pkey.go
new file mode 100644
index 000000000..75bb12c73
--- /dev/null
+++ b/util/syspolicy/pkey/pkey.go
@@ -0,0 +1,133 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package pkey defines the keys used to store system policies in the registry.
+//
+// This is a leaf package meant to only contain string constants, not code.
+package pkey
+
+// Key is a string that uniquely identifies a policy and must remain unchanged
+// once established and documented for a given policy setting. It may contain
+// alphanumeric characters and zero or more [KeyPathSeparator]s to group
+// individual policy settings into categories.
+type Key string
+
+// KeyPathSeparator allows logical grouping of policy settings into categories.
+const KeyPathSeparator = '/'
+
+// The const block below lists known policy keys.
+// When adding a key to this list, remember to add a corresponding
+// [setting.Definition] to syspolicy/policy_keys.go's [implicitDefinitions].
+// Otherwise, the TestKnownKeysRegistered test will fail as a reminder.
+
+const (
+ // Keys with a string value
+ ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
+ LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
+ Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
+ // ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
+ // Exit node ID takes precedence over exit node IP.
+ // To find the node ID, go to /api.md#device.
+ ExitNodeID Key = "ExitNodeID"
+ ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
+
+ // Keys with a string value that specifies an option: "always", "never", "user-decides".
+ // The default is "user-decides" unless otherwise stated. Enforcement of
+ // these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
+ // typically hide menu items related to policies that are enforced.
+ EnableIncomingConnections Key = "AllowIncomingConnections"
+ EnableServerMode Key = "UnattendedMode"
+ ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
+ EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
+ EnableTailscaleSubnets Key = "UseTailscaleSubnets"
+ // CheckUpdates is the key to signal if the updater should periodically
+ // check for updates.
+ CheckUpdates Key = "CheckUpdates"
+ // ApplyUpdates is the key to signal if updates should be automatically
+ // installed. Its value is "InstallUpdates" because of an awkwardly-named
+ // visibility option "ApplyUpdates" on MacOS.
+ ApplyUpdates Key = "InstallUpdates"
+ // EnableRunExitNode controls if the device acts as an exit node. Even when
+ // running as an exit node, the device must be approved by a tailnet
+ // administrator. Its name is slightly awkward because RunExitNodeVisibility
+ // predates this option but is preserved for backwards compatibility.
+ EnableRunExitNode Key = "AdvertiseExitNode"
+
+ // Keys with a string value that controls visibility: "show", "hide".
+ // The default is "show" unless otherwise stated. Enforcement of these
+ // policies is typically performed by the UI code for the relevant operating
+ // system.
+ AdminConsoleVisibility Key = "AdminConsole"
+ NetworkDevicesVisibility Key = "NetworkDevices"
+ TestMenuVisibility Key = "TestMenu"
+ UpdateMenuVisibility Key = "UpdateMenu"
+ ResetToDefaultsVisibility Key = "ResetToDefaults"
+ // RunExitNodeVisibility controls if the "run as exit node" menu item is
+ // visible, without controlling the setting itself. This is preserved for
+ // backwards compatibility but prefer EnableRunExitNode in new deployments.
+ RunExitNodeVisibility Key = "RunExitNode"
+ PreferencesMenuVisibility Key = "PreferencesMenu"
+ ExitNodeMenuVisibility Key = "ExitNodesPicker"
+ // AutoUpdateVisibility is the key to signal if the menu item for automatic
+ // installation of updates should be visible. It is only used by macsys
+ // installations and uses the Sparkle naming convention, even though it does
+ // not actually control updates, merely the UI for that setting.
+ AutoUpdateVisibility Key = "ApplyUpdates"
+ // SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
+ // When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
+ SuggestedExitNodeVisibility Key = "SuggestedExitNode"
+ // OnboardingFlowVisibility controls the visibility of the onboarding flow in the client GUI.
+ // When this system policy is set to 'hide', the onboarding flow is never shown to the user.
+ OnboardingFlowVisibility Key = "OnboardingFlow"
+
+ // Keys with a string value formatted for use with time.ParseDuration().
+ KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
+
+ // Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
+ // DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
+ // The default is 0 unless otherwise stated.
+ LogSCMInteractions Key = "LogSCMInteractions"
+ FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
+
+ // PostureChecking indicates if posture checking is enabled and the client shall gather
+ // posture data.
+ // Key is a string value that specifies an option: "always", "never", "user-decides".
+ // The default is "user-decides" unless otherwise stated.
+ PostureChecking Key = "PostureChecking"
+ // DeviceSerialNumber is the serial number of the device that is running Tailscale.
+ // This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
+ // We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
+ DeviceSerialNumber Key = "DeviceSerialNumber"
+
+ // ManagedByOrganizationName indicates the name of the organization managing the Tailscale
+ // install. It is displayed inside the client UI in a prominent location.
+ ManagedByOrganizationName Key = "ManagedByOrganizationName"
+ // ManagedByCaption is an info message displayed inside the client UI as a caption when
+ // ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
+ // for Tailscale within the organization.
+ ManagedByCaption Key = "ManagedByCaption"
+ // ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
+ // organization. A button in the client UI provides easy access to this URL.
+ ManagedByURL Key = "ManagedByURL"
+
+ // AuthKey is an auth key that will be used to login whenever the backend starts. This can be used to
+ // automatically authenticate managed devices, without requiring user interaction.
+ AuthKey Key = "AuthKey"
+
+ // MachineCertificateSubject is the exact name of a Subject that needs
+ // to be present in an identity's certificate chain to sign a RegisterRequest,
+ // formatted as per pkix.Name.String(). The Subject may be that of the identity
+ // itself, an intermediate CA or the root CA.
+ //
+ // Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
+ MachineCertificateSubject Key = "MachineCertificateSubject"
+
+ // Hostname is the hostname of the device that is running Tailscale.
+ // When this policy is set, it overrides the hostname that the client
+ // would otherwise obtain from the OS, e.g. by calling os.Hostname().
+ Hostname Key = "Hostname"
+
+ // Keys with a string array value.
+ // AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
+ AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
+)
diff --git a/util/syspolicy/policy_keys.go b/util/syspolicy/policy_keys.go
index 35a36130e..dee80ff3c 100644
--- a/util/syspolicy/policy_keys.go
+++ b/util/syspolicy/policy_keys.go
@@ -6,176 +6,54 @@ package syspolicy
import (
"tailscale.com/types/lazy"
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/testenv"
)
-// Key is a string that uniquely identifies a policy and must remain unchanged
-// once established and documented for a given policy setting. It may contain
-// alphanumeric characters and zero or more [KeyPathSeparator]s to group
-// individual policy settings into categories.
-type Key = setting.Key
-
-// The const block below lists known policy keys.
-// When adding a key to this list, remember to add a corresponding
-// [setting.Definition] to [implicitDefinitions] below.
-// Otherwise, the [TestKnownKeysRegistered] test will fail as a reminder.
-
-const (
- // Keys with a string value
- ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
- LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
- Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
- // ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
- // Exit node ID takes precedence over exit node IP.
- // To find the node ID, go to /api.md#device.
- ExitNodeID Key = "ExitNodeID"
- ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
-
- // Keys with a string value that specifies an option: "always", "never", "user-decides".
- // The default is "user-decides" unless otherwise stated. Enforcement of
- // these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
- // typically hide menu items related to policies that are enforced.
- EnableIncomingConnections Key = "AllowIncomingConnections"
- EnableServerMode Key = "UnattendedMode"
- ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
- EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
- EnableTailscaleSubnets Key = "UseTailscaleSubnets"
- // CheckUpdates is the key to signal if the updater should periodically
- // check for updates.
- CheckUpdates Key = "CheckUpdates"
- // ApplyUpdates is the key to signal if updates should be automatically
- // installed. Its value is "InstallUpdates" because of an awkwardly-named
- // visibility option "ApplyUpdates" on MacOS.
- ApplyUpdates Key = "InstallUpdates"
- // EnableRunExitNode controls if the device acts as an exit node. Even when
- // running as an exit node, the device must be approved by a tailnet
- // administrator. Its name is slightly awkward because RunExitNodeVisibility
- // predates this option but is preserved for backwards compatibility.
- EnableRunExitNode Key = "AdvertiseExitNode"
-
- // Keys with a string value that controls visibility: "show", "hide".
- // The default is "show" unless otherwise stated. Enforcement of these
- // policies is typically performed by the UI code for the relevant operating
- // system.
- AdminConsoleVisibility Key = "AdminConsole"
- NetworkDevicesVisibility Key = "NetworkDevices"
- TestMenuVisibility Key = "TestMenu"
- UpdateMenuVisibility Key = "UpdateMenu"
- ResetToDefaultsVisibility Key = "ResetToDefaults"
- // RunExitNodeVisibility controls if the "run as exit node" menu item is
- // visible, without controlling the setting itself. This is preserved for
- // backwards compatibility but prefer EnableRunExitNode in new deployments.
- RunExitNodeVisibility Key = "RunExitNode"
- PreferencesMenuVisibility Key = "PreferencesMenu"
- ExitNodeMenuVisibility Key = "ExitNodesPicker"
- // AutoUpdateVisibility is the key to signal if the menu item for automatic
- // installation of updates should be visible. It is only used by macsys
- // installations and uses the Sparkle naming convention, even though it does
- // not actually control updates, merely the UI for that setting.
- AutoUpdateVisibility Key = "ApplyUpdates"
- // SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
- // When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
- SuggestedExitNodeVisibility Key = "SuggestedExitNode"
- // OnboardingFlowVisibility controls the visibility of the onboarding flow in the client GUI.
- // When this system policy is set to 'hide', the onboarding flow is never shown to the user.
- OnboardingFlowVisibility Key = "OnboardingFlow"
-
- // Keys with a string value formatted for use with time.ParseDuration().
- KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
-
- // Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
- // DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
- // The default is 0 unless otherwise stated.
- LogSCMInteractions Key = "LogSCMInteractions"
- FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
-
- // PostureChecking indicates if posture checking is enabled and the client shall gather
- // posture data.
- // Key is a string value that specifies an option: "always", "never", "user-decides".
- // The default is "user-decides" unless otherwise stated.
- PostureChecking Key = "PostureChecking"
- // DeviceSerialNumber is the serial number of the device that is running Tailscale.
- // This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
- // We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
- DeviceSerialNumber Key = "DeviceSerialNumber"
-
- // ManagedByOrganizationName indicates the name of the organization managing the Tailscale
- // install. It is displayed inside the client UI in a prominent location.
- ManagedByOrganizationName Key = "ManagedByOrganizationName"
- // ManagedByCaption is an info message displayed inside the client UI as a caption when
- // ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
- // for Tailscale within the organization.
- ManagedByCaption Key = "ManagedByCaption"
- // ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
- // organization. A button in the client UI provides easy access to this URL.
- ManagedByURL Key = "ManagedByURL"
-
- // AuthKey is an auth key that will be used to login whenever the backend starts. This can be used to
- // automatically authenticate managed devices, without requiring user interaction.
- AuthKey Key = "AuthKey"
-
- // MachineCertificateSubject is the exact name of a Subject that needs
- // to be present in an identity's certificate chain to sign a RegisterRequest,
- // formatted as per pkix.Name.String(). The Subject may be that of the identity
- // itself, an intermediate CA or the root CA.
- //
- // Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
- MachineCertificateSubject Key = "MachineCertificateSubject"
-
- // Hostname is the hostname of the device that is running Tailscale.
- // When this policy is set, it overrides the hostname that the client
- // would otherwise obtain from the OS, e.g. by calling os.Hostname().
- Hostname Key = "Hostname"
-
- // Keys with a string array value.
- // AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
- AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
-)
-
// implicitDefinitions is a list of [setting.Definition] that will be registered
// automatically when the policy setting definitions are first used by the syspolicy package hierarchy.
// This includes the first time a policy needs to be read from any source.
var implicitDefinitions = []*setting.Definition{
// Device policy settings (can only be configured on a per-device basis):
- setting.NewDefinition(AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue),
- setting.NewDefinition(ApplyUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(AuthKey, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(CheckUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(ControlURL, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(DeviceSerialNumber, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(EnableIncomingConnections, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(EnableRunExitNode, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(EnableServerMode, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(EnableTailscaleDNS, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(EnableTailscaleSubnets, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(ExitNodeAllowLANAccess, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(ExitNodeID, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(ExitNodeIP, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(FlushDNSOnSessionUnlock, setting.DeviceSetting, setting.BooleanValue),
- setting.NewDefinition(Hostname, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(LogSCMInteractions, setting.DeviceSetting, setting.BooleanValue),
- setting.NewDefinition(LogTarget, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(MachineCertificateSubject, setting.DeviceSetting, setting.StringValue),
- setting.NewDefinition(PostureChecking, setting.DeviceSetting, setting.PreferenceOptionValue),
- setting.NewDefinition(Tailnet, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue),
+ setting.NewDefinition(pkey.ApplyUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.AuthKey, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.CheckUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.ControlURL, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.DeviceSerialNumber, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.EnableIncomingConnections, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.EnableRunExitNode, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.EnableServerMode, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.EnableTailscaleDNS, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.EnableTailscaleSubnets, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.ExitNodeAllowLANAccess, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.ExitNodeID, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.ExitNodeIP, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.FlushDNSOnSessionUnlock, setting.DeviceSetting, setting.BooleanValue),
+ setting.NewDefinition(pkey.Hostname, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.LogSCMInteractions, setting.DeviceSetting, setting.BooleanValue),
+ setting.NewDefinition(pkey.LogTarget, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.MachineCertificateSubject, setting.DeviceSetting, setting.StringValue),
+ setting.NewDefinition(pkey.PostureChecking, setting.DeviceSetting, setting.PreferenceOptionValue),
+ setting.NewDefinition(pkey.Tailnet, setting.DeviceSetting, setting.StringValue),
// User policy settings (can be configured on a user- or device-basis):
- setting.NewDefinition(AdminConsoleVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(AutoUpdateVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(ExitNodeMenuVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(KeyExpirationNoticeTime, setting.UserSetting, setting.DurationValue),
- setting.NewDefinition(ManagedByCaption, setting.UserSetting, setting.StringValue),
- setting.NewDefinition(ManagedByOrganizationName, setting.UserSetting, setting.StringValue),
- setting.NewDefinition(ManagedByURL, setting.UserSetting, setting.StringValue),
- setting.NewDefinition(NetworkDevicesVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(PreferencesMenuVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(ResetToDefaultsVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(RunExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(SuggestedExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(TestMenuVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(UpdateMenuVisibility, setting.UserSetting, setting.VisibilityValue),
- setting.NewDefinition(OnboardingFlowVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.AdminConsoleVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.AutoUpdateVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.ExitNodeMenuVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.KeyExpirationNoticeTime, setting.UserSetting, setting.DurationValue),
+ setting.NewDefinition(pkey.ManagedByCaption, setting.UserSetting, setting.StringValue),
+ setting.NewDefinition(pkey.ManagedByOrganizationName, setting.UserSetting, setting.StringValue),
+ setting.NewDefinition(pkey.ManagedByURL, setting.UserSetting, setting.StringValue),
+ setting.NewDefinition(pkey.NetworkDevicesVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.PreferencesMenuVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.ResetToDefaultsVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.RunExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.SuggestedExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.TestMenuVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.UpdateMenuVisibility, setting.UserSetting, setting.VisibilityValue),
+ setting.NewDefinition(pkey.OnboardingFlowVisibility, setting.UserSetting, setting.VisibilityValue),
}
func init() {
@@ -199,7 +77,7 @@ var implicitDefinitionMap lazy.SyncValue[setting.DefinitionMap]
// WellKnownSettingDefinition returns a well-known, implicit setting definition by its key,
// or an [ErrNoSuchKey] if a policy setting with the specified key does not exist
// among implicit policy definitions.
-func WellKnownSettingDefinition(k Key) (*setting.Definition, error) {
+func WellKnownSettingDefinition(k pkey.Key) (*setting.Definition, error) {
m, err := implicitDefinitionMap.GetErr(func() (setting.DefinitionMap, error) {
return setting.DefinitionMapOf(implicitDefinitions)
})
@@ -215,6 +93,7 @@ func WellKnownSettingDefinition(k Key) (*setting.Definition, error) {
// RegisterWellKnownSettingsForTest registers all implicit setting definitions
// for the duration of the test.
func RegisterWellKnownSettingsForTest(tb TB) {
+ tb.Skip("XXX delete this func")
tb.Helper()
err := setting.SetDefinitionsForTest(tb, implicitDefinitions...)
if err != nil {
diff --git a/util/syspolicy/policy_keys_test.go b/util/syspolicy/policy_keys_test.go
index 4d3260f3e..77c76d2ff 100644
--- a/util/syspolicy/policy_keys_test.go
+++ b/util/syspolicy/policy_keys_test.go
@@ -14,9 +14,12 @@ import (
"strconv"
"testing"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
+type Key = pkey.Key
+
func TestKnownKeysRegistered(t *testing.T) {
keyConsts, err := listStringConsts[Key]("policy_keys.go")
if err != nil {
diff --git a/util/syspolicy/policyclient/policyclient.go b/util/syspolicy/policyclient/policyclient.go
new file mode 100644
index 000000000..99d5a120c
--- /dev/null
+++ b/util/syspolicy/policyclient/policyclient.go
@@ -0,0 +1,51 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package policyclient contains the minimal syspolicy interface as needed by
+// client code using syspolicy without bringing in the entire syspolicy
+// universe.
+package policyclient
+
+import "tailscale.com/util/syspolicy/pkey"
+
+type Client interface {
+ // GetString returns a string policy setting with the specified key,
+ // or defaultValue if it does not exist.
+ GetString(key pkey.Key, defaultValue string) (string, error)
+
+ GetStringArray(key pkey.Key, defaultValue []string) ([]string, error)
+
+ GetBoolean(key pkey.Key, defaultValue bool) (bool, error)
+
+ SetDebugLoggingEnabled(enabled bool)
+
+ RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error)
+}
+
+// NoPolicyClient is a no-op implementation of Client that only
+// returns default values.
+type NoPolicyClient struct{}
+
+var _ Client = NoPolicyClient{}
+
+func (NoPolicyClient) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
+ return defaultValue, nil
+}
+
+func (NoPolicyClient) GetString(key pkey.Key, defaultValue string) (string, error) {
+ return defaultValue, nil
+}
+
+func (NoPolicyClient) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
+ return defaultValue, nil
+}
+
+func (NoPolicyClient) SetDebugLoggingEnabled(enabled bool) {}
+
+func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {
+ return func() {}, nil
+}
+
+type PolicyChange interface {
+ HasChanged(key pkey.Key) bool
+}
diff --git a/util/syspolicy/rsop/change_callbacks.go b/util/syspolicy/rsop/change_callbacks.go
index b962f30c0..6eb15c905 100644
--- a/util/syspolicy/rsop/change_callbacks.go
+++ b/util/syspolicy/rsop/change_callbacks.go
@@ -11,6 +11,8 @@ import (
"tailscale.com/util/set"
"tailscale.com/util/syspolicy/internal/loggerx"
+ "tailscale.com/util/syspolicy/pkey"
+ "tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/syspolicy/setting"
)
@@ -20,7 +22,7 @@ type Change[T any] struct {
}
// PolicyChangeCallback is a function called whenever a policy changes.
-type PolicyChangeCallback func(*PolicyChange)
+type PolicyChangeCallback func(policyclient.PolicyChange)
// PolicyChange describes a policy change.
type PolicyChange struct {
@@ -38,7 +40,7 @@ func (c PolicyChange) Old() *setting.Snapshot {
}
// HasChanged reports whether a policy setting with the specified [setting.Key], has changed.
-func (c PolicyChange) HasChanged(key setting.Key) bool {
+func (c PolicyChange) HasChanged(key pkey.Key) bool {
new, newErr := c.snapshots.New.GetErr(key)
old, oldErr := c.snapshots.Old.GetErr(key)
if newErr != nil && oldErr != nil {
diff --git a/util/syspolicy/rsop/resultant_policy_test.go b/util/syspolicy/rsop/resultant_policy_test.go
index e4bfb1a88..1e32fec26 100644
--- a/util/syspolicy/rsop/resultant_policy_test.go
+++ b/util/syspolicy/rsop/resultant_policy_test.go
@@ -15,6 +15,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"tailscale.com/tstest"
+ "tailscale.com/util/syspolicy/pkey"
+ "tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/syspolicy/source"
@@ -80,7 +82,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
type sourceConfig struct {
name string
scope setting.PolicyScope
- settingKey setting.Key
+ settingKey pkey.Key
settingValue string
wantEffective bool
}
@@ -113,7 +115,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
},
@@ -129,7 +131,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
},
@@ -159,7 +161,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
"TestKeyC": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
@@ -191,7 +193,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
}, setting.DeviceScope),
@@ -245,7 +247,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("TestValueF", nil, setting.NewNamedOrigin("TestSourceF", setting.DeviceScope)),
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
"TestKeyC": setting.RawItemWith("TestValueE", nil, setting.NewNamedOrigin("TestSourceE", setting.DeviceScope)),
@@ -263,7 +265,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
}, setting.CurrentUserScope, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
},
@@ -288,7 +290,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
"TestKeyB": setting.RawItemWith("UserValue", nil, setting.NewNamedOrigin("TestSourceUser", setting.CurrentUserScope)),
}, setting.CurrentUserScope),
@@ -321,7 +323,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: true,
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
"TestKeyB": setting.RawItemWith("ProfileValue", nil, setting.NewNamedOrigin("TestSourceProfile", setting.CurrentProfileScope)),
}, setting.CurrentUserScope),
@@ -347,7 +349,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
wantEffective: false, // Registering a user source should have no impact on the device policy.
},
},
- wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
}, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
},
@@ -497,61 +499,61 @@ func TestPolicyFor(t *testing.T) {
func TestPolicyChangeHasChanged(t *testing.T) {
tests := []struct {
name string
- old, new map[setting.Key]setting.RawItem
- wantChanged []setting.Key
- wantUnchanged []setting.Key
+ old, new map[pkey.Key]setting.RawItem
+ wantChanged []pkey.Key
+ wantUnchanged []pkey.Key
}{
{
name: "String-Settings",
- old: map[setting.Key]setting.RawItem{
+ old: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf("Old"),
"UnchangedSetting": setting.RawItemOf("Value"),
},
- new: map[setting.Key]setting.RawItem{
+ new: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf("New"),
"UnchangedSetting": setting.RawItemOf("Value"),
},
- wantChanged: []setting.Key{"ChangedSetting"},
- wantUnchanged: []setting.Key{"UnchangedSetting"},
+ wantChanged: []pkey.Key{"ChangedSetting"},
+ wantUnchanged: []pkey.Key{"UnchangedSetting"},
},
{
name: "UInt64-Settings",
- old: map[setting.Key]setting.RawItem{
+ old: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf(uint64(0)),
"UnchangedSetting": setting.RawItemOf(uint64(42)),
},
- new: map[setting.Key]setting.RawItem{
+ new: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf(uint64(1)),
"UnchangedSetting": setting.RawItemOf(uint64(42)),
},
- wantChanged: []setting.Key{"ChangedSetting"},
- wantUnchanged: []setting.Key{"UnchangedSetting"},
+ wantChanged: []pkey.Key{"ChangedSetting"},
+ wantUnchanged: []pkey.Key{"UnchangedSetting"},
},
{
name: "StringSlice-Settings",
- old: map[setting.Key]setting.RawItem{
+ old: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf([]string{"Chicago"}),
"UnchangedSetting": setting.RawItemOf([]string{"String1", "String2"}),
},
- new: map[setting.Key]setting.RawItem{
+ new: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf([]string{"New York"}),
"UnchangedSetting": setting.RawItemOf([]string{"String1", "String2"}),
},
- wantChanged: []setting.Key{"ChangedSetting"},
- wantUnchanged: []setting.Key{"UnchangedSetting"},
+ wantChanged: []pkey.Key{"ChangedSetting"},
+ wantUnchanged: []pkey.Key{"UnchangedSetting"},
},
{
name: "Int8-Settings", // We don't have actual int8 settings, but this should still work.
- old: map[setting.Key]setting.RawItem{
+ old: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf(int8(0)),
"UnchangedSetting": setting.RawItemOf(int8(42)),
},
- new: map[setting.Key]setting.RawItem{
+ new: map[pkey.Key]setting.RawItem{
"ChangedSetting": setting.RawItemOf(int8(1)),
"UnchangedSetting": setting.RawItemOf(int8(42)),
},
- wantChanged: []setting.Key{"ChangedSetting"},
- wantUnchanged: []setting.Key{"UnchangedSetting"},
+ wantChanged: []pkey.Key{"ChangedSetting"},
+ wantUnchanged: []pkey.Key{"UnchangedSetting"},
},
}
for _, tt := range tests {
@@ -601,8 +603,8 @@ func TestChangePolicySetting(t *testing.T) {
}
// Subscribe to the policy change callback...
- policyChanged := make(chan *PolicyChange)
- unregister := policy.RegisterChangeCallback(func(pc *PolicyChange) { policyChanged <- pc })
+ policyChanged := make(chan policyclient.PolicyChange)
+ unregister := policy.RegisterChangeCallback(func(pc policyclient.PolicyChange) { policyChanged <- pc })
t.Cleanup(unregister)
// ...make the change, and measure the time between initiating the change
@@ -629,10 +631,10 @@ func TestChangePolicySetting(t *testing.T) {
if change.HasChanged(settingB.Key()) {
t.Errorf("Policy setting %q was unexpectedly changed", settingB.Key())
}
- if _, ok := change.Old().GetSetting(settingA.Key()); ok {
+ if _, ok := change.(*PolicyChange).Old().GetSetting(settingA.Key()); ok {
t.Fatalf("Policy setting %q unexpectedly exists", settingA.Key())
}
- if gotValue := change.New().Get(settingA.Key()); gotValue != wantValueA {
+ if gotValue := change.(*PolicyChange).New().Get(settingA.Key()); gotValue != wantValueA {
t.Errorf("Policy setting %q: got %q; want %q", settingA.Key(), gotValue, wantValueA)
}
@@ -682,10 +684,10 @@ drain:
if change.HasChanged(settingA.Key()) {
t.Errorf("Policy setting %q was unexpectedly changed", settingA.Key())
}
- if _, ok := change.Old().GetSetting(settingB.Key()); ok {
+ if _, ok := change.(*PolicyChange).Old().GetSetting(settingB.Key()); ok {
t.Fatalf("Policy setting %q unexpectedly exists", settingB.Key())
}
- if gotValue := change.New().Get(settingB.Key()); gotValue != wantValueB {
+ if gotValue := change.(*PolicyChange).New().Get(settingB.Key()); gotValue != wantValueB {
t.Errorf("Policy setting %q: got %q; want %q", settingB.Key(), gotValue, wantValueB)
}
@@ -852,8 +854,8 @@ func TestReplacePolicySource(t *testing.T) {
}
// Subscribe to the policy change callback.
- policyChanged := make(chan *PolicyChange, 1)
- unregister := policy.RegisterChangeCallback(func(pc *PolicyChange) { policyChanged <- pc })
+ policyChanged := make(chan policyclient.PolicyChange, 1)
+ unregister := policy.RegisterChangeCallback(func(pc policyclient.PolicyChange) { policyChanged <- pc })
t.Cleanup(unregister)
// Now, let's replace the initial store with the new store.
diff --git a/util/syspolicy/setting/key.go b/util/syspolicy/setting/key.go
deleted file mode 100644
index aa7606d36..000000000
--- a/util/syspolicy/setting/key.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package setting
-
-// Key is a string that uniquely identifies a policy and must remain unchanged
-// once established and documented for a given policy setting. It may contain
-// alphanumeric characters and zero or more [KeyPathSeparator]s to group
-// individual policy settings into categories.
-type Key string
-
-// KeyPathSeparator allows logical grouping of policy settings into categories.
-const KeyPathSeparator = '/'
diff --git a/util/syspolicy/setting/raw_item.go b/util/syspolicy/setting/raw_item.go
index cf46e54b7..86b93feaa 100644
--- a/util/syspolicy/setting/raw_item.go
+++ b/util/syspolicy/setting/raw_item.go
@@ -11,6 +11,7 @@ import (
"github.com/go-json-experiment/json/jsontext"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
+ "tailscale.com/util/syspolicy/pkey"
)
// RawItem contains a raw policy setting value as read from a policy store, or an
@@ -159,4 +160,4 @@ func (v *RawValue) UnmarshalJSON(b []byte) error {
}
// RawValues is a map of keyed setting values that can be read from a JSON.
-type RawValues map[Key]RawValue
+type RawValues map[pkey.Key]RawValue
diff --git a/util/syspolicy/setting/setting.go b/util/syspolicy/setting/setting.go
index 70fb0a931..7038d3ccc 100644
--- a/util/syspolicy/setting/setting.go
+++ b/util/syspolicy/setting/setting.go
@@ -16,6 +16,7 @@ import (
"tailscale.com/types/lazy"
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
)
// Scope indicates the broadest scope at which a policy setting may apply,
@@ -133,7 +134,7 @@ type ValueType interface {
// Definition defines policy key, scope and value type.
type Definition struct {
- key Key
+ key pkey.Key
scope Scope
typ Type
platforms PlatformList
@@ -141,12 +142,12 @@ type Definition struct {
// NewDefinition returns a new [Definition] with the specified
// key, scope, type and supported platforms (see [PlatformList]).
-func NewDefinition(k Key, s Scope, t Type, platforms ...string) *Definition {
+func NewDefinition(k pkey.Key, s Scope, t Type, platforms ...string) *Definition {
return &Definition{key: k, scope: s, typ: t, platforms: platforms}
}
// Key returns a policy setting's identifier.
-func (d *Definition) Key() Key {
+func (d *Definition) Key() pkey.Key {
if d == nil {
return ""
}
@@ -207,7 +208,7 @@ func (d *Definition) Equal(d2 *Definition) bool {
}
// DefinitionMap is a map of setting [Definition] by [Key].
-type DefinitionMap map[Key]*Definition
+type DefinitionMap map[pkey.Key]*Definition
var (
definitions lazy.SyncValue[DefinitionMap]
@@ -223,7 +224,7 @@ var (
// invoking any functions that use the registered policy definitions. This
// includes calling [Definitions] or [DefinitionOf] directly, or reading any
// policy settings via syspolicy.
-func Register(k Key, s Scope, t Type, platforms ...string) {
+func Register(k pkey.Key, s Scope, t Type, platforms ...string) {
RegisterDefinition(NewDefinition(k, s, t, platforms...))
}
@@ -289,7 +290,7 @@ func SetDefinitionsForTest(tb lazy.TB, ds ...*Definition) error {
// DefinitionOf returns a setting definition by key,
// or [ErrNoSuchKey] if the specified key does not exist,
// or an error if there are conflicting policy definitions.
-func DefinitionOf(k Key) (*Definition, error) {
+func DefinitionOf(k pkey.Key) (*Definition, error) {
ds, err := settingDefinitions()
if err != nil {
return nil, err
diff --git a/util/syspolicy/setting/setting_test.go b/util/syspolicy/setting/setting_test.go
index 3cc08e7da..6605e2fd1 100644
--- a/util/syspolicy/setting/setting_test.go
+++ b/util/syspolicy/setting/setting_test.go
@@ -11,8 +11,11 @@ import (
"tailscale.com/types/lazy"
"tailscale.com/types/ptr"
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
)
+type Key = pkey.Key
+
func TestSettingDefinition(t *testing.T) {
tests := []struct {
name string
diff --git a/util/syspolicy/setting/snapshot.go b/util/syspolicy/setting/snapshot.go
index 0af2bae0f..b415a976a 100644
--- a/util/syspolicy/setting/snapshot.go
+++ b/util/syspolicy/setting/snapshot.go
@@ -14,34 +14,35 @@ import (
"github.com/go-json-experiment/json/jsontext"
xmaps "golang.org/x/exp/maps"
"tailscale.com/util/deephash"
+ "tailscale.com/util/syspolicy/pkey"
)
// Snapshot is an immutable collection of ([Key], [RawItem]) pairs, representing
// a set of policy settings applied at a specific moment in time.
// A nil pointer to [Snapshot] is valid.
type Snapshot struct {
- m map[Key]RawItem
+ m map[pkey.Key]RawItem
sig deephash.Sum // of m
summary Summary
}
// NewSnapshot returns a new [Snapshot] with the specified items and options.
-func NewSnapshot(items map[Key]RawItem, opts ...SummaryOption) *Snapshot {
+func NewSnapshot(items map[pkey.Key]RawItem, opts ...SummaryOption) *Snapshot {
return &Snapshot{m: xmaps.Clone(items), sig: deephash.Hash(&items), summary: SummaryWith(opts...)}
}
// All returns an iterator over policy settings in s. The iteration order is not
// specified and is not guaranteed to be the same from one call to the next.
-func (s *Snapshot) All() iter.Seq2[Key, RawItem] {
+func (s *Snapshot) All() iter.Seq2[pkey.Key, RawItem] {
if s == nil {
- return func(yield func(Key, RawItem) bool) {}
+ return func(yield func(pkey.Key, RawItem) bool) {}
}
return maps.All(s.m)
}
// Get returns the value of the policy setting with the specified key
// or nil if it is not configured or has an error.
-func (s *Snapshot) Get(k Key) any {
+func (s *Snapshot) Get(k pkey.Key) any {
v, _ := s.GetErr(k)
return v
}
@@ -49,7 +50,7 @@ func (s *Snapshot) Get(k Key) any {
// GetErr returns the value of the policy setting with the specified key,
// [ErrNotConfigured] if it is not configured, or an error returned by
// the policy Store if the policy setting could not be read.
-func (s *Snapshot) GetErr(k Key) (any, error) {
+func (s *Snapshot) GetErr(k pkey.Key) (any, error) {
if s != nil {
if s, ok := s.m[k]; ok {
return s.Value(), s.Error()
@@ -61,7 +62,7 @@ func (s *Snapshot) GetErr(k Key) (any, error) {
// GetSetting returns the untyped policy setting with the specified key and true
// if a policy setting with such key has been configured;
// otherwise, it returns zero, false.
-func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
+func (s *Snapshot) GetSetting(k pkey.Key) (setting RawItem, ok bool) {
setting, ok = s.m[k]
return setting, ok
}
@@ -93,9 +94,9 @@ func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
// Keys return an iterator over keys in s. The iteration order is not specified
// and is not guaranteed to be the same from one call to the next.
-func (s *Snapshot) Keys() iter.Seq[Key] {
+func (s *Snapshot) Keys() iter.Seq[pkey.Key] {
if s.m == nil {
- return func(yield func(Key) bool) {}
+ return func(yield func(pkey.Key) bool) {}
}
return maps.Keys(s.m)
}
@@ -143,8 +144,8 @@ func (s *Snapshot) String() string {
// snapshotJSON holds JSON-marshallable data for [Snapshot].
type snapshotJSON struct {
- Summary Summary `json:",omitzero"`
- Settings map[Key]RawItem `json:",omitempty"`
+ Summary Summary `json:",omitzero"`
+ Settings map[pkey.Key]RawItem `json:",omitempty"`
}
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
@@ -208,7 +209,7 @@ func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
}
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
}
- m := make(map[Key]RawItem, snapshot1.Len()+snapshot2.Len())
+ m := make(map[pkey.Key]RawItem, snapshot1.Len()+snapshot2.Len())
xmaps.Copy(m, snapshot1.m)
xmaps.Copy(m, snapshot2.m) // snapshot2 has higher precedence
return &Snapshot{m, deephash.Hash(&m), SummaryWith(summaryOpts...)}
diff --git a/util/syspolicy/source/env_policy_store.go b/util/syspolicy/source/env_policy_store.go
index 299132b4e..be363b79a 100644
--- a/util/syspolicy/source/env_policy_store.go
+++ b/util/syspolicy/source/env_policy_store.go
@@ -11,6 +11,7 @@ import (
"strings"
"unicode/utf8"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
@@ -22,7 +23,7 @@ var _ Store = (*EnvPolicyStore)(nil)
type EnvPolicyStore struct{}
// ReadString implements [Store].
-func (s *EnvPolicyStore) ReadString(key setting.Key) (string, error) {
+func (s *EnvPolicyStore) ReadString(key pkey.Key) (string, error) {
_, str, err := s.lookupSettingVariable(key)
if err != nil {
return "", err
@@ -31,7 +32,7 @@ func (s *EnvPolicyStore) ReadString(key setting.Key) (string, error) {
}
// ReadUInt64 implements [Store].
-func (s *EnvPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
+func (s *EnvPolicyStore) ReadUInt64(key pkey.Key) (uint64, error) {
name, str, err := s.lookupSettingVariable(key)
if err != nil {
return 0, err
@@ -47,7 +48,7 @@ func (s *EnvPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
}
// ReadBoolean implements [Store].
-func (s *EnvPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
+func (s *EnvPolicyStore) ReadBoolean(key pkey.Key) (bool, error) {
name, str, err := s.lookupSettingVariable(key)
if err != nil {
return false, err
@@ -63,7 +64,7 @@ func (s *EnvPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
}
// ReadStringArray implements [Store].
-func (s *EnvPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
+func (s *EnvPolicyStore) ReadStringArray(key pkey.Key) ([]string, error) {
_, str, err := s.lookupSettingVariable(key)
if err != nil || str == "" {
return nil, err
@@ -79,7 +80,7 @@ func (s *EnvPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
return res[0:dst], nil
}
-func (s *EnvPolicyStore) lookupSettingVariable(key setting.Key) (name, value string, err error) {
+func (s *EnvPolicyStore) lookupSettingVariable(key pkey.Key) (name, value string, err error) {
name, err = keyToEnvVarName(key)
if err != nil {
return "", "", err
@@ -103,7 +104,7 @@ var (
//
// It's fine to use this in [EnvPolicyStore] without caching variable names since it's not a hot path.
// [EnvPolicyStore] is not a [Changeable] policy store, so the conversion will only happen once.
-func keyToEnvVarName(key setting.Key) (string, error) {
+func keyToEnvVarName(key pkey.Key) (string, error) {
if len(key) == 0 {
return "", errEmptyKey
}
@@ -135,7 +136,7 @@ func keyToEnvVarName(key setting.Key) (string, error) {
}
case isDigit(c):
split = currentWord.Len() > 0 && !isDigit(key[i-1])
- case c == setting.KeyPathSeparator:
+ case c == pkey.KeyPathSeparator:
words = append(words, currentWord.String())
currentWord.Reset()
continue
diff --git a/util/syspolicy/source/env_policy_store_test.go b/util/syspolicy/source/env_policy_store_test.go
index 9eacf6378..3255095b2 100644
--- a/util/syspolicy/source/env_policy_store_test.go
+++ b/util/syspolicy/source/env_policy_store_test.go
@@ -11,13 +11,14 @@ import (
"strconv"
"testing"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
func TestKeyToEnvVarName(t *testing.T) {
tests := []struct {
name string
- key setting.Key
+ key pkey.Key
want string // suffix after "TS_DEBUGSYSPOLICY_"
wantErr error
}{
@@ -166,7 +167,7 @@ func TestEnvPolicyStore(t *testing.T) {
}
tests := []struct {
name string
- key setting.Key
+ key pkey.Key
lookup func(string) (string, bool)
want any
wantErr error
diff --git a/util/syspolicy/source/policy_reader.go b/util/syspolicy/source/policy_reader.go
index a1bd3147e..e6360e5f8 100644
--- a/util/syspolicy/source/policy_reader.go
+++ b/util/syspolicy/source/policy_reader.go
@@ -16,6 +16,7 @@ import (
"tailscale.com/util/set"
"tailscale.com/util/syspolicy/internal/loggerx"
"tailscale.com/util/syspolicy/internal/metrics"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
@@ -138,9 +139,9 @@ func (r *Reader) reload(force bool) (*setting.Snapshot, error) {
metrics.Reset(r.origin)
- var m map[setting.Key]setting.RawItem
+ var m map[pkey.Key]setting.RawItem
if lastPolicyCount := r.lastPolicy.Len(); lastPolicyCount > 0 {
- m = make(map[setting.Key]setting.RawItem, lastPolicyCount)
+ m = make(map[pkey.Key]setting.RawItem, lastPolicyCount)
}
for _, s := range r.settings {
if !r.origin.Scope().IsConfigurableSetting(s) {
diff --git a/util/syspolicy/source/policy_reader_test.go b/util/syspolicy/source/policy_reader_test.go
index 57676e67d..06246a209 100644
--- a/util/syspolicy/source/policy_reader_test.go
+++ b/util/syspolicy/source/policy_reader_test.go
@@ -9,6 +9,7 @@ import (
"time"
"tailscale.com/util/must"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
@@ -72,7 +73,7 @@ func TestReaderLifecycle(t *testing.T) {
initWant: setting.NewSnapshot(nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
addStrings: []TestSetting[string]{TestSettingOf("StringValue", "S1")},
addStringLists: []TestSetting[[]string]{TestSettingOf("StringListValue", []string{"S1", "S2", "S3"})},
- newWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ newWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"StringValue": setting.RawItemWith("S1", nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
"StringListValue": setting.RawItemWith([]string{"S1", "S2", "S3"}, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
}, setting.NewNamedOrigin("Test", setting.DeviceScope)),
@@ -136,7 +137,7 @@ func TestReaderLifecycle(t *testing.T) {
TestSettingOf("PreferenceOptionValue", "always"),
TestSettingOf("VisibilityValue", "show"),
},
- initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ initWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"DurationValue": setting.RawItemWith(must.Get(time.ParseDuration("2h30m")), nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
"PreferenceOptionValue": setting.RawItemWith(setting.AlwaysByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
"VisibilityValue": setting.RawItemWith(setting.VisibleByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
@@ -165,7 +166,7 @@ func TestReaderLifecycle(t *testing.T) {
initUInt64s: []TestSetting[uint64]{
TestSettingOf[uint64]("VisibilityValue", 42), // type mismatch
},
- initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ initWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"DurationValue1": setting.RawItemWith(nil, setting.NewErrorText("time: invalid duration \"soon\""), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
"DurationValue2": setting.RawItemWith(nil, setting.NewErrorText("bang!"), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
"PreferenceOptionValue": setting.RawItemWith(setting.ShowChoiceByPolicy, nil, setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
@@ -277,7 +278,7 @@ func TestReadingSession(t *testing.T) {
t.Fatalf("the session was closed prematurely")
}
- want := setting.NewSnapshot(map[setting.Key]setting.RawItem{
+ want := setting.NewSnapshot(map[pkey.Key]setting.RawItem{
"StringValue": setting.RawItemWith("S1", nil, origin),
}, origin)
if got := session.GetSettings(); !got.Equal(want) {
diff --git a/util/syspolicy/source/policy_source.go b/util/syspolicy/source/policy_source.go
index 7f2821b59..c4774217c 100644
--- a/util/syspolicy/source/policy_source.go
+++ b/util/syspolicy/source/policy_source.go
@@ -13,6 +13,7 @@ import (
"io"
"tailscale.com/types/lazy"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
@@ -31,19 +32,19 @@ type Store interface {
// ReadString returns the value of a [setting.StringValue] with the specified key,
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
// an error on failure.
- ReadString(key setting.Key) (string, error)
+ ReadString(key pkey.Key) (string, error)
// ReadUInt64 returns the value of a [setting.IntegerValue] with the specified key,
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
// an error on failure.
- ReadUInt64(key setting.Key) (uint64, error)
+ ReadUInt64(key pkey.Key) (uint64, error)
// ReadBoolean returns the value of a [setting.BooleanValue] with the specified key,
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
// an error on failure.
- ReadBoolean(key setting.Key) (bool, error)
+ ReadBoolean(key pkey.Key) (bool, error)
// ReadStringArray returns the value of a [setting.StringListValue] with the specified key,
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
// an error on failure.
- ReadStringArray(key setting.Key) ([]string, error)
+ ReadStringArray(key pkey.Key) ([]string, error)
}
// Lockable is an optional interface that [Store] implementations may support.
diff --git a/util/syspolicy/source/policy_store_windows.go b/util/syspolicy/source/policy_store_windows.go
index 86e2254e0..fd2045bed 100644
--- a/util/syspolicy/source/policy_store_windows.go
+++ b/util/syspolicy/source/policy_store_windows.go
@@ -12,6 +12,7 @@ import (
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"tailscale.com/util/set"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/winutil/gp"
)
@@ -238,7 +239,7 @@ func (ps *PlatformPolicyStore) onChange() {
// ReadString retrieves a string policy with the specified key.
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
-func (ps *PlatformPolicyStore) ReadString(key setting.Key) (val string, err error) {
+func (ps *PlatformPolicyStore) ReadString(key pkey.Key) (val string, err error) {
return getPolicyValue(ps, key,
func(key registry.Key, valueName string) (string, error) {
val, _, err := key.GetStringValue(valueName)
@@ -248,7 +249,7 @@ func (ps *PlatformPolicyStore) ReadString(key setting.Key) (val string, err erro
// ReadUInt64 retrieves an integer policy with the specified key.
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
-func (ps *PlatformPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
+func (ps *PlatformPolicyStore) ReadUInt64(key pkey.Key) (uint64, error) {
return getPolicyValue(ps, key,
func(key registry.Key, valueName string) (uint64, error) {
val, _, err := key.GetIntegerValue(valueName)
@@ -258,7 +259,7 @@ func (ps *PlatformPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
// ReadBoolean retrieves a boolean policy with the specified key.
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
-func (ps *PlatformPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
+func (ps *PlatformPolicyStore) ReadBoolean(key pkey.Key) (bool, error) {
return getPolicyValue(ps, key,
func(key registry.Key, valueName string) (bool, error) {
val, _, err := key.GetIntegerValue(valueName)
@@ -271,7 +272,7 @@ func (ps *PlatformPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
// ReadString retrieves a multi-string policy with the specified key.
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
-func (ps *PlatformPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
+func (ps *PlatformPolicyStore) ReadStringArray(key pkey.Key) ([]string, error) {
return getPolicyValue(ps, key,
func(key registry.Key, valueName string) ([]string, error) {
val, _, err := key.GetStringsValue(valueName)
@@ -318,16 +319,16 @@ func (ps *PlatformPolicyStore) ReadStringArray(key setting.Key) ([]string, error
// while everything preceding it is considered a subpath (relative to the {HKLM,HKCU}\Software\Policies\Tailscale key).
// If there are no [setting.KeyPathSeparator]s in the key, the policy setting value
// is meant to be stored directly under {HKLM,HKCU}\Software\Policies\Tailscale.
-func splitSettingKey(key setting.Key) (path, valueName string) {
- if idx := strings.LastIndexByte(string(key), setting.KeyPathSeparator); idx != -1 {
- path = strings.ReplaceAll(string(key[:idx]), string(setting.KeyPathSeparator), `\`)
+func splitSettingKey(key pkey.Key) (path, valueName string) {
+ if idx := strings.LastIndexByte(string(key), pkey.KeyPathSeparator); idx != -1 {
+ path = strings.ReplaceAll(string(key[:idx]), string(pkey.KeyPathSeparator), `\`)
valueName = string(key[idx+1:])
return path, valueName
}
return "", string(key)
}
-func getPolicyValue[T any](ps *PlatformPolicyStore, key setting.Key, getter registryValueGetter[T]) (T, error) {
+func getPolicyValue[T any](ps *PlatformPolicyStore, key pkey.Key, getter registryValueGetter[T]) (T, error) {
var zero T
ps.mu.Lock()
diff --git a/util/syspolicy/source/policy_store_windows_test.go b/util/syspolicy/source/policy_store_windows_test.go
index 33f85dc0b..4ab1da805 100644
--- a/util/syspolicy/source/policy_store_windows_test.go
+++ b/util/syspolicy/source/policy_store_windows_test.go
@@ -19,6 +19,7 @@ import (
"tailscale.com/tstest"
"tailscale.com/util/cibuild"
"tailscale.com/util/mak"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/winutil"
"tailscale.com/util/winutil/gp"
@@ -31,7 +32,7 @@ import (
type subkeyStrings []string
type testPolicyValue struct {
- name setting.Key
+ name pkey.Key
value any
}
@@ -100,7 +101,7 @@ func TestReadPolicyStore(t *testing.T) {
t.Skipf("test requires running as elevated user")
}
tests := []struct {
- name setting.Key
+ name pkey.Key
newValue any
legacyValue any
want any
@@ -269,7 +270,7 @@ func TestPolicyStoreChangeNotifications(t *testing.T) {
func TestSplitSettingKey(t *testing.T) {
tests := []struct {
name string
- key setting.Key
+ key pkey.Key
wantPath string
wantValue string
}{
diff --git a/util/syspolicy/source/test_store.go b/util/syspolicy/source/test_store.go
index e6c09d6b0..b94978d13 100644
--- a/util/syspolicy/source/test_store.go
+++ b/util/syspolicy/source/test_store.go
@@ -13,6 +13,7 @@ import (
"tailscale.com/util/set"
"tailscale.com/util/slicesx"
"tailscale.com/util/syspolicy/internal"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
)
@@ -31,7 +32,7 @@ type TestValueType interface {
// TestSetting is a policy setting in a [TestStore].
type TestSetting[T TestValueType] struct {
// Key is the setting's unique identifier.
- Key setting.Key
+ Key pkey.Key
// Error is the error to be returned by the [TestStore] when reading
// a policy setting with the specified key.
Error error
@@ -43,20 +44,20 @@ type TestSetting[T TestValueType] struct {
// TestSettingOf returns a [TestSetting] representing a policy setting
// configured with the specified key and value.
-func TestSettingOf[T TestValueType](key setting.Key, value T) TestSetting[T] {
+func TestSettingOf[T TestValueType](key pkey.Key, value T) TestSetting[T] {
return TestSetting[T]{Key: key, Value: value}
}
// TestSettingWithError returns a [TestSetting] representing a policy setting
// with the specified key and error.
-func TestSettingWithError[T TestValueType](key setting.Key, err error) TestSetting[T] {
+func TestSettingWithError[T TestValueType](key pkey.Key, err error) TestSetting[T] {
return TestSetting[T]{Key: key, Error: err}
}
// testReadOperation describes a single policy setting read operation.
type testReadOperation struct {
// Key is the setting's unique identifier.
- Key setting.Key
+ Key pkey.Key
// Type is a value type of a read operation.
// [setting.BooleanValue], [setting.IntegerValue], [setting.StringValue] or [setting.StringListValue]
Type setting.Type
@@ -65,7 +66,7 @@ type testReadOperation struct {
// TestExpectedReads is the number of read operations with the specified details.
type TestExpectedReads struct {
// Key is the setting's unique identifier.
- Key setting.Key
+ Key pkey.Key
// Type is a value type of a read operation.
// [setting.BooleanValue], [setting.IntegerValue], [setting.StringValue] or [setting.StringListValue]
Type setting.Type
@@ -87,8 +88,8 @@ type TestStore struct {
storeLockCount atomic.Int32
mu sync.RWMutex
- suspendCount int // change callback are suspended if > 0
- mr, mw map[setting.Key]any // maps for reading and writing; they're the same unless the store is suspended.
+ suspendCount int // change callback are suspended if > 0
+ mr, mw map[pkey.Key]any // maps for reading and writing; they're the same unless the store is suspended.
cbs set.HandleSet[func()]
closed bool
@@ -99,7 +100,7 @@ type TestStore struct {
// NewTestStore returns a new [TestStore].
// The tb will be used to report coding errors detected by the [TestStore].
func NewTestStore(tb internal.TB) *TestStore {
- m := make(map[setting.Key]any)
+ m := make(map[pkey.Key]any)
store := &TestStore{
tb: tb,
done: make(chan struct{}),
@@ -155,7 +156,7 @@ func (s *TestStore) RegisterChangeCallback(callback func()) (unregister func(),
}
// ReadString implements [Store].
-func (s *TestStore) ReadString(key setting.Key) (string, error) {
+func (s *TestStore) ReadString(key pkey.Key) (string, error) {
defer s.recordRead(key, setting.StringValue)
s.mu.RLock()
defer s.mu.RUnlock()
@@ -174,7 +175,7 @@ func (s *TestStore) ReadString(key setting.Key) (string, error) {
}
// ReadUInt64 implements [Store].
-func (s *TestStore) ReadUInt64(key setting.Key) (uint64, error) {
+func (s *TestStore) ReadUInt64(key pkey.Key) (uint64, error) {
defer s.recordRead(key, setting.IntegerValue)
s.mu.RLock()
defer s.mu.RUnlock()
@@ -193,7 +194,7 @@ func (s *TestStore) ReadUInt64(key setting.Key) (uint64, error) {
}
// ReadBoolean implements [Store].
-func (s *TestStore) ReadBoolean(key setting.Key) (bool, error) {
+func (s *TestStore) ReadBoolean(key pkey.Key) (bool, error) {
defer s.recordRead(key, setting.BooleanValue)
s.mu.RLock()
defer s.mu.RUnlock()
@@ -212,7 +213,7 @@ func (s *TestStore) ReadBoolean(key setting.Key) (bool, error) {
}
// ReadStringArray implements [Store].
-func (s *TestStore) ReadStringArray(key setting.Key) ([]string, error) {
+func (s *TestStore) ReadStringArray(key pkey.Key) ([]string, error) {
defer s.recordRead(key, setting.StringListValue)
s.mu.RLock()
defer s.mu.RUnlock()
@@ -230,7 +231,7 @@ func (s *TestStore) ReadStringArray(key setting.Key) ([]string, error) {
return slice, nil
}
-func (s *TestStore) recordRead(key setting.Key, typ setting.Type) {
+func (s *TestStore) recordRead(key pkey.Key, typ setting.Type) {
s.readsMu.Lock()
op := testReadOperation{key, typ}
num := s.reads[op]
@@ -318,15 +319,15 @@ func (s *TestStore) Resume() {
// SetBooleans sets the specified boolean settings in s.
func (s *TestStore) SetBooleans(settings ...TestSetting[bool]) {
s.storeLock.Lock()
- for _, setting := range settings {
- if setting.Key == "" {
+ for _, ts := range settings {
+ if ts.Key == "" {
s.tb.Fatal("empty keys disallowed")
}
s.mu.Lock()
- if setting.Error != nil {
- mak.Set(&s.mw, setting.Key, any(setting.Error))
+ if ts.Error != nil {
+ mak.Set(&s.mw, ts.Key, any(ts.Error))
} else {
- mak.Set(&s.mw, setting.Key, any(setting.Value))
+ mak.Set(&s.mw, ts.Key, any(ts.Value))
}
s.mu.Unlock()
}
@@ -337,15 +338,15 @@ func (s *TestStore) SetBooleans(settings ...TestSetting[bool]) {
// SetUInt64s sets the specified integer settings in s.
func (s *TestStore) SetUInt64s(settings ...TestSetting[uint64]) {
s.storeLock.Lock()
- for _, setting := range settings {
- if setting.Key == "" {
+ for _, ts := range settings {
+ if ts.Key == "" {
s.tb.Fatal("empty keys disallowed")
}
s.mu.Lock()
- if setting.Error != nil {
- mak.Set(&s.mw, setting.Key, any(setting.Error))
+ if ts.Error != nil {
+ mak.Set(&s.mw, ts.Key, any(ts.Error))
} else {
- mak.Set(&s.mw, setting.Key, any(setting.Value))
+ mak.Set(&s.mw, ts.Key, any(ts.Value))
}
s.mu.Unlock()
}
@@ -356,15 +357,15 @@ func (s *TestStore) SetUInt64s(settings ...TestSetting[uint64]) {
// SetStrings sets the specified string settings in s.
func (s *TestStore) SetStrings(settings ...TestSetting[string]) {
s.storeLock.Lock()
- for _, setting := range settings {
- if setting.Key == "" {
+ for _, ts := range settings {
+ if ts.Key == "" {
s.tb.Fatal("empty keys disallowed")
}
s.mu.Lock()
- if setting.Error != nil {
- mak.Set(&s.mw, setting.Key, any(setting.Error))
+ if ts.Error != nil {
+ mak.Set(&s.mw, ts.Key, any(ts.Error))
} else {
- mak.Set(&s.mw, setting.Key, any(setting.Value))
+ mak.Set(&s.mw, ts.Key, any(ts.Value))
}
s.mu.Unlock()
}
@@ -375,15 +376,15 @@ func (s *TestStore) SetStrings(settings ...TestSetting[string]) {
// SetStrings sets the specified string list settings in s.
func (s *TestStore) SetStringLists(settings ...TestSetting[[]string]) {
s.storeLock.Lock()
- for _, setting := range settings {
- if setting.Key == "" {
+ for _, ts := range settings {
+ if ts.Key == "" {
s.tb.Fatal("empty keys disallowed")
}
s.mu.Lock()
- if setting.Error != nil {
- mak.Set(&s.mw, setting.Key, any(setting.Error))
+ if ts.Error != nil {
+ mak.Set(&s.mw, ts.Key, any(ts.Error))
} else {
- mak.Set(&s.mw, setting.Key, any(setting.Value))
+ mak.Set(&s.mw, ts.Key, any(ts.Value))
}
s.mu.Unlock()
}
@@ -392,7 +393,7 @@ func (s *TestStore) SetStringLists(settings ...TestSetting[[]string]) {
}
// Delete deletes the specified settings from s.
-func (s *TestStore) Delete(keys ...setting.Key) {
+func (s *TestStore) Delete(keys ...pkey.Key) {
s.storeLock.Lock()
for _, key := range keys {
s.mu.Lock()
diff --git a/util/syspolicy/syspolicy.go b/util/syspolicy/syspolicy.go
index d925731c3..82a3848f4 100644
--- a/util/syspolicy/syspolicy.go
+++ b/util/syspolicy/syspolicy.go
@@ -17,6 +17,7 @@ import (
"time"
"tailscale.com/util/syspolicy/internal/loggerx"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/rsop"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/syspolicy/source"
@@ -47,6 +48,7 @@ func RegisterStore(name string, scope setting.PolicyScope, store source.Store) (
// MustRegisterStoreForTest is like [rsop.RegisterStoreForTest], but it fails the test if the store could not be registered.
func MustRegisterStoreForTest(tb TB, name string, scope setting.PolicyScope, store source.Store) *rsop.StoreRegistration {
+ tb.Skip("XXXX delete MustRegisterStoreForTest")
tb.Helper()
reg, err := rsop.RegisterStoreForTest(tb, name, scope, store)
if err != nil {
@@ -57,25 +59,25 @@ func MustRegisterStoreForTest(tb TB, name string, scope setting.PolicyScope, sto
// GetString returns a string policy setting with the specified key,
// or defaultValue if it does not exist.
-func GetString(key Key, defaultValue string) (string, error) {
+func GetString(key pkey.Key, defaultValue string) (string, error) {
return getCurrentPolicySettingValue(key, defaultValue)
}
// GetUint64 returns a numeric policy setting with the specified key,
// or defaultValue if it does not exist.
-func GetUint64(key Key, defaultValue uint64) (uint64, error) {
+func GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
return getCurrentPolicySettingValue(key, defaultValue)
}
// GetBoolean returns a boolean policy setting with the specified key,
// or defaultValue if it does not exist.
-func GetBoolean(key Key, defaultValue bool) (bool, error) {
+func GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
return getCurrentPolicySettingValue(key, defaultValue)
}
// GetStringArray returns a multi-string policy setting with the specified key,
// or defaultValue if it does not exist.
-func GetStringArray(key Key, defaultValue []string) ([]string, error) {
+func GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
return getCurrentPolicySettingValue(key, defaultValue)
}
@@ -85,7 +87,7 @@ func GetStringArray(key Key, defaultValue []string) ([]string, error) {
// the authority to set. It describes user-decides/always/never options, where
// "always" and "never" remove the user's ability to make a selection. If not
// present or set to a different value, "user-decides" is the default.
-func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
+func GetPreferenceOption(name pkey.Key) (setting.PreferenceOption, error) {
return getCurrentPolicySettingValue(name, setting.ShowChoiceByPolicy)
}
@@ -94,7 +96,7 @@ func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
// for UI elements. The registry value should be a string set to "show" (return
// true) or "hide" (return true). If not present or set to a different value,
// "show" (return false) is the default.
-func GetVisibility(name Key) (setting.Visibility, error) {
+func GetVisibility(name pkey.Key) (setting.Visibility, error) {
return getCurrentPolicySettingValue(name, setting.VisibleByPolicy)
}
@@ -103,7 +105,7 @@ func GetVisibility(name Key) (setting.Visibility, error) {
// action. The registry value should be a string that time.ParseDuration
// understands. If the registry value is "" or can not be processed,
// defaultValue is returned instead.
-func GetDuration(name Key, defaultValue time.Duration) (time.Duration, error) {
+func GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
d, err := getCurrentPolicySettingValue(name, defaultValue)
if err != nil {
return d, err
@@ -128,7 +130,7 @@ func RegisterChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), er
// specified by its key from the [rsop.Policy] of the [setting.DefaultScope]. It
// returns def if the policy setting is not configured, or an error if it has
// an error or could not be converted to the specified type T.
-func getCurrentPolicySettingValue[T setting.ValueType](key Key, def T) (T, error) {
+func getCurrentPolicySettingValue[T setting.ValueType](key pkey.Key, def T) (T, error) {
effective, err := rsop.PolicyFor(setting.DefaultScope())
if err != nil {
return def, err
diff --git a/util/syspolicy/syspolicy_test.go b/util/syspolicy/syspolicy_test.go
index a70a49d39..427f8ad65 100644
--- a/util/syspolicy/syspolicy_test.go
+++ b/util/syspolicy/syspolicy_test.go
@@ -12,6 +12,7 @@ import (
"tailscale.com/types/logger"
"tailscale.com/util/syspolicy/internal/loggerx"
"tailscale.com/util/syspolicy/internal/metrics"
+ "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/syspolicy/source"
)
@@ -31,7 +32,7 @@ func TestGetString(t *testing.T) {
}{
{
name: "read existing value",
- key: AdminConsoleVisibility,
+ key: pkey.AdminConsoleVisibility,
handlerValue: "hide",
wantValue: "hide",
wantMetrics: []metrics.TestState{
@@ -41,13 +42,13 @@ func TestGetString(t *testing.T) {
},
{
name: "read non-existing value",
- key: EnableServerMode,
+ key: pkey.EnableServerMode,
handlerError: ErrNotConfigured,
wantError: nil,
},
{
name: "read non-existing value, non-blank default",
- key: EnableServerMode,
+ key: pkey.EnableServerMode,
handlerError: ErrNotConfigured,
defaultValue: "test",
wantValue: "test",
@@ -55,7 +56,7 @@ func TestGetString(t *testing.T) {
},
{
name: "reading value returns other error",
- key: NetworkDevicesVisibility,
+ key: pkey.NetworkDevicesVisibility,
handlerError: someOtherError,
wantError: someOtherError,
wantMetrics: []metrics.TestState{
@@ -111,27 +112,27 @@ func TestGetUint64(t *testing.T) {
}{
{
name: "read existing value",
- key: LogSCMInteractions,
+ key: pkey.LogSCMInteractions,
handlerValue: 1,
wantValue: 1,
},
{
name: "read non-existing value",
- key: LogSCMInteractions,
+ key: pkey.LogSCMInteractions,
handlerValue: 0,
handlerError: ErrNotConfigured,
wantValue: 0,
},
{
name: "read non-existing value, non-zero default",
- key: LogSCMInteractions,
+ key: pkey.LogSCMInteractions,
defaultValue: 2,
handlerError: ErrNotConfigured,
wantValue: 2,
},
{
name: "reading value returns other error",
- key: FlushDNSOnSessionUnlock,
+ key: pkey.FlushDNSOnSessionUnlock,
handlerError: someOtherError,
wantError: someOtherError,
},
@@ -178,7 +179,7 @@ func TestGetBoolean(t *testing.T) {
}{
{
name: "read existing value",
- key: FlushDNSOnSessionUnlock,
+ key: pkey.FlushDNSOnSessionUnlock,
handlerValue: true,
wantValue: true,
wantMetrics: []metrics.TestState{
@@ -188,14 +189,14 @@ func TestGetBoolean(t *testing.T) {
},
{
name: "read non-existing value",
- key: LogSCMInteractions,
+ key: pkey.LogSCMInteractions,
handlerValue: false,
handlerError: ErrNotConfigured,
wantValue: false,
},
{
name: "reading value returns other error",
- key: FlushDNSOnSessionUnlock,
+ key: pkey.FlushDNSOnSessionUnlock,
handlerError: someOtherError,
wantError: someOtherError, // expect error...
defaultValue: true,
@@ -253,7 +254,7 @@ func TestGetPreferenceOption(t *testing.T) {
}{
{
name: "always by policy",
- key: EnableIncomingConnections,
+ key: pkey.EnableIncomingConnections,
handlerValue: "always",
wantValue: setting.AlwaysByPolicy,
wantMetrics: []metrics.TestState{
@@ -263,7 +264,7 @@ func TestGetPreferenceOption(t *testing.T) {
},
{
name: "never by policy",
- key: EnableIncomingConnections,
+ key: pkey.EnableIncomingConnections,
handlerValue: "never",
wantValue: setting.NeverByPolicy,
wantMetrics: []metrics.TestState{
@@ -273,7 +274,7 @@ func TestGetPreferenceOption(t *testing.T) {
},
{
name: "use default",
- key: EnableIncomingConnections,
+ key: pkey.EnableIncomingConnections,
handlerValue: "",
wantValue: setting.ShowChoiceByPolicy,
wantMetrics: []metrics.TestState{
@@ -283,13 +284,13 @@ func TestGetPreferenceOption(t *testing.T) {
},
{
name: "read non-existing value",
- key: EnableIncomingConnections,
+ key: pkey.EnableIncomingConnections,
handlerError: ErrNotConfigured,
wantValue: setting.ShowChoiceByPolicy,
},
{
name: "other error is returned",
- key: EnableIncomingConnections,
+ key: pkey.EnableIncomingConnections,
handlerError: someOtherError,
wantValue: setting.ShowChoiceByPolicy,
wantError: someOtherError,
@@ -346,7 +347,7 @@ func TestGetVisibility(t *testing.T) {
}{
{
name: "hidden by policy",
- key: AdminConsoleVisibility,
+ key: pkey.AdminConsoleVisibility,
handlerValue: "hide",
wantValue: setting.HiddenByPolicy,
wantMetrics: []metrics.TestState{
@@ -356,7 +357,7 @@ func TestGetVisibility(t *testing.T) {
},
{
name: "visibility default",
- key: AdminConsoleVisibility,
+ key: pkey.AdminConsoleVisibility,
handlerValue: "show",
wantValue: setting.VisibleByPolicy,
wantMetrics: []metrics.TestState{
@@ -366,14 +367,14 @@ func TestGetVisibility(t *testing.T) {
},
{
name: "read non-existing value",
- key: AdminConsoleVisibility,
+ key: pkey.AdminConsoleVisibility,
handlerValue: "show",
handlerError: ErrNotConfigured,
wantValue: setting.VisibleByPolicy,
},
{
name: "other error is returned",
- key: AdminConsoleVisibility,
+ key: pkey.AdminConsoleVisibility,
handlerValue: "show",
handlerError: someOtherError,
wantValue: setting.VisibleByPolicy,
@@ -432,7 +433,7 @@ func TestGetDuration(t *testing.T) {
}{
{
name: "read existing value",
- key: KeyExpirationNoticeTime,
+ key: pkey.KeyExpirationNoticeTime,
handlerValue: "2h",
wantValue: 2 * time.Hour,
defaultValue: 24 * time.Hour,
@@ -443,7 +444,7 @@ func TestGetDuration(t *testing.T) {
},
{
name: "invalid duration value",
- key: KeyExpirationNoticeTime,
+ key: pkey.KeyExpirationNoticeTime,
handlerValue: "-20",
wantValue: 24 * time.Hour,
wantError: errors.New(`time: missing unit in duration "-20"`),
@@ -455,21 +456,21 @@ func TestGetDuration(t *testing.T) {
},
{
name: "read non-existing value",
- key: KeyExpirationNoticeTime,
+ key: pkey.KeyExpirationNoticeTime,
handlerError: ErrNotConfigured,
wantValue: 24 * time.Hour,
defaultValue: 24 * time.Hour,
},
{
name: "read non-existing value different default",
- key: KeyExpirationNoticeTime,
+ key: pkey.KeyExpirationNoticeTime,
handlerError: ErrNotConfigured,
wantValue: 0 * time.Second,
defaultValue: 0 * time.Second,
},
{
name: "other error is returned",
- key: KeyExpirationNoticeTime,
+ key: pkey.KeyExpirationNoticeTime,
handlerError: someOtherError,
wantValue: 24 * time.Hour,
wantError: someOtherError,
@@ -528,7 +529,7 @@ func TestGetStringArray(t *testing.T) {
}{
{
name: "read existing value",
- key: AllowedSuggestedExitNodes,
+ key: pkey.AllowedSuggestedExitNodes,
handlerValue: []string{"foo", "bar"},
wantValue: []string{"foo", "bar"},
wantMetrics: []metrics.TestState{
@@ -538,13 +539,13 @@ func TestGetStringArray(t *testing.T) {
},
{
name: "read non-existing value",
- key: AllowedSuggestedExitNodes,
+ key: pkey.AllowedSuggestedExitNodes,
handlerError: ErrNotConfigured,
wantError: nil,
},
{
name: "read non-existing value, non nil default",
- key: AllowedSuggestedExitNodes,
+ key: pkey.AllowedSuggestedExitNodes,
handlerError: ErrNotConfigured,
defaultValue: []string{"foo", "bar"},
wantValue: []string{"foo", "bar"},
@@ -552,7 +553,7 @@ func TestGetStringArray(t *testing.T) {
},
{
name: "reading value returns other error",
- key: AllowedSuggestedExitNodes,
+ key: pkey.AllowedSuggestedExitNodes,
handlerError: someOtherError,
wantError: someOtherError,
wantMetrics: []metrics.TestState{
@@ -606,11 +607,11 @@ func BenchmarkGetString(b *testing.B) {
RegisterWellKnownSettingsForTest(b)
wantControlURL := "https://login.tailscale.com"
- registerSingleSettingStoreForTest(b, source.TestSettingOf(ControlURL, wantControlURL))
+ registerSingleSettingStoreForTest(b, source.TestSettingOf(pkey.ControlURL, wantControlURL))
b.ResetTimer()
for i := 0; i < b.N; i++ {
- gotControlURL, _ := GetString(ControlURL, "https://controlplane.tailscale.com")
+ gotControlURL, _ := GetString(pkey.ControlURL, "https://controlplane.tailscale.com")
if gotControlURL != wantControlURL {
b.Fatalf("got %v; want %v", gotControlURL, wantControlURL)
}