diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 6b149e5f5..ccea25a8a 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -174,6 +174,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy/internal/metrics+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/ipn+ tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index 4b1e4a1e4..a0214575b 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -955,6 +955,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy/internal/metrics+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/control/controlclient+ tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 02ffec0ea..7f09be33f 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -195,6 +195,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy/internal/metrics+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/ipn+ tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index c2d9f3d00..46efa5b21 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -432,6 +432,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy/internal/metrics+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/cmd/tailscaled+ tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 06d366aa6..f55535470 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -65,6 +65,7 @@ import ( "tailscale.com/util/multierr" "tailscale.com/util/osshare" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/version" "tailscale.com/version/distro" "tailscale.com/wgengine" @@ -1011,6 +1012,6 @@ func defaultEncryptState() bool { // (plan9/FreeBSD/etc). return false } - v, _ := syspolicy.GetBoolean(syspolicy.EncryptState, false) + v, _ := syspolicy.GetBoolean(pkey.EncryptState, false) return v } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 1b5068892..2d4e71d3c 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -56,6 +56,7 @@ import ( "tailscale.com/types/logid" "tailscale.com/util/osdiag" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/winutil" "tailscale.com/util/winutil/gp" "tailscale.com/version" @@ -155,7 +156,7 @@ func runWindowsService(pol *logpolicy.Policy) error { if syslog, err := eventlog.Open(serviceName); err == nil { syslogf = func(format string, args ...any) { - if logSCMInteractions, _ := syspolicy.GetBoolean(syspolicy.LogSCMInteractions, false); logSCMInteractions { + if logSCMInteractions, _ := syspolicy.GetBoolean(pkey.LogSCMInteractions, false); logSCMInteractions { syslog.Info(0, fmt.Sprintf(format, args...)) } } @@ -389,8 +390,7 @@ func handleSessionChange(chgRequest svc.ChangeRequest) { if chgRequest.Cmd != svc.SessionChange || chgRequest.EventType != windows.WTS_SESSION_UNLOCK { return } - - if flushDNSOnSessionUnlock, _ := syspolicy.GetBoolean(syspolicy.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock { + if flushDNSOnSessionUnlock, _ := syspolicy.GetBoolean(pkey.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock { log.Printf("Received WTS_SESSION_UNLOCK event, initiating DNS flush.") go func() { err := dns.Flush() diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index e8bc2b254..f1e22efbf 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -384,6 +384,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/control/controlclient+ tailscale.com/util/syspolicy/rsop from tailscale.com/ipn/ipnlocal+ tailscale.com/util/syspolicy/setting from tailscale.com/client/local+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 78a86e935..cee938779 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -54,6 +54,7 @@ import ( "tailscale.com/util/multierr" "tailscale.com/util/singleflight" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/systemd" "tailscale.com/util/testenv" "tailscale.com/util/zstdframe" @@ -616,7 +617,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new return regen, opt.URL, nil, err } - tailnet, err := syspolicy.GetString(syspolicy.Tailnet, "") + tailnet, err := syspolicy.GetString(pkey.Tailnet, "") if err != nil { c.logf("unable to provide Tailnet field in register request. err: %v", err) } diff --git a/control/controlclient/sign_supported.go b/control/controlclient/sign_supported.go index a5d42ad7d..fab7cd16b 100644 --- a/control/controlclient/sign_supported.go +++ b/control/controlclient/sign_supported.go @@ -19,6 +19,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" ) // getMachineCertificateSubject returns the exact name of a Subject that needs @@ -31,7 +32,7 @@ import ( // // Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA" func getMachineCertificateSubject() string { - machineCertSubject, _ := syspolicy.GetString(syspolicy.MachineCertificateSubject, "") + machineCertSubject, _ := syspolicy.GetString(pkey.MachineCertificateSubject, "") return machineCertSubject } diff --git a/ipn/desktop/extension.go b/ipn/desktop/extension.go index f204a90de..15d239f89 100644 --- a/ipn/desktop/extension.go +++ b/ipn/desktop/extension.go @@ -19,6 +19,7 @@ import ( "tailscale.com/ipn/ipnext" "tailscale.com/types/logger" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" ) // featureName is the name of the feature implemented by this package. @@ -135,7 +136,7 @@ func (e *desktopSessionsExt) getBackgroundProfile(profiles ipnext.ProfileStore) e.mu.Lock() defer e.mu.Unlock() - if alwaysOn, _ := syspolicy.GetBoolean(syspolicy.AlwaysOn, false); !alwaysOn { + if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); !alwaysOn { // If the Always-On mode is disabled, there's no background profile // as far as the desktop session extension is concerned. return ipn.LoginProfileView{} diff --git a/ipn/ipnauth/policy.go b/ipn/ipnauth/policy.go index aa4ec4100..36004b293 100644 --- a/ipn/ipnauth/policy.go +++ b/ipn/ipnauth/policy.go @@ -11,6 +11,7 @@ import ( "tailscale.com/ipn" "tailscale.com/tailcfg" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" ) type actorWithPolicyChecks struct{ Actor } @@ -50,10 +51,10 @@ func (a actorWithPolicyChecks) CheckProfileAccess(profile ipn.LoginProfileView, // TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver] // and corp to this package. func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditFn AuditLogFunc) error { - if alwaysOn, _ := syspolicy.GetBoolean(syspolicy.AlwaysOn, false); !alwaysOn { + if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); !alwaysOn { return nil } - if allowWithReason, _ := syspolicy.GetBoolean(syspolicy.AlwaysOnOverrideWithReason, false); !allowWithReason { + if allowWithReason, _ := syspolicy.GetBoolean(pkey.AlwaysOnOverrideWithReason, false); !allowWithReason { return errors.New("disconnect not allowed: always-on mode is enabled") } if reason == "" { diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index 4b91c3cb9..8c3bf7b26 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -30,6 +30,7 @@ import ( "tailscale.com/util/goroutines" "tailscale.com/util/set" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/version" "tailscale.com/version/distro" ) @@ -342,7 +343,7 @@ func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http // this will first check syspolicy, MDM settings like Registry // on Windows or defaults on macOS. If they are not set, it falls // back to the cli-flag, `--posture-checking`. - choice, err := syspolicy.GetPreferenceOption(syspolicy.PostureChecking) + choice, err := syspolicy.GetPreferenceOption(pkey.PostureChecking) if err != nil { b.logf( "c2n: failed to read PostureChecking from syspolicy, returning default from CLI: %s; got error: %s", diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 43d7e1216..bcfb99b09 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -108,6 +108,7 @@ import ( "tailscale.com/util/set" "tailscale.com/util/slicesx" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/rsop" "tailscale.com/util/systemd" "tailscale.com/util/testenv" @@ -1762,51 +1763,51 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control } type preferencePolicyInfo struct { - key syspolicy.Key + key pkey.Key get func(ipn.PrefsView) bool set func(*ipn.Prefs, bool) } var preferencePolicies = []preferencePolicyInfo{ { - key: syspolicy.EnableIncomingConnections, + key: pkey.EnableIncomingConnections, // Allow Incoming (used by the UI) is the negation of ShieldsUp (used by the // backend), so this has to convert between the two conventions. get: func(p ipn.PrefsView) bool { return !p.ShieldsUp() }, set: func(p *ipn.Prefs, v bool) { p.ShieldsUp = !v }, }, { - key: syspolicy.EnableServerMode, + key: pkey.EnableServerMode, get: func(p ipn.PrefsView) bool { return p.ForceDaemon() }, set: func(p *ipn.Prefs, v bool) { p.ForceDaemon = v }, }, { - key: syspolicy.ExitNodeAllowLANAccess, + key: pkey.ExitNodeAllowLANAccess, get: func(p ipn.PrefsView) bool { return p.ExitNodeAllowLANAccess() }, set: func(p *ipn.Prefs, v bool) { p.ExitNodeAllowLANAccess = v }, }, { - key: syspolicy.EnableTailscaleDNS, + key: pkey.EnableTailscaleDNS, get: func(p ipn.PrefsView) bool { return p.CorpDNS() }, set: func(p *ipn.Prefs, v bool) { p.CorpDNS = v }, }, { - key: syspolicy.EnableTailscaleSubnets, + key: pkey.EnableTailscaleSubnets, get: func(p ipn.PrefsView) bool { return p.RouteAll() }, set: func(p *ipn.Prefs, v bool) { p.RouteAll = v }, }, { - key: syspolicy.CheckUpdates, + key: pkey.CheckUpdates, get: func(p ipn.PrefsView) bool { return p.AutoUpdate().Check }, set: func(p *ipn.Prefs, v bool) { p.AutoUpdate.Check = v }, }, { - key: syspolicy.ApplyUpdates, + key: pkey.ApplyUpdates, get: func(p ipn.PrefsView) bool { v, _ := p.AutoUpdate().Apply.Get(); return v }, set: func(p *ipn.Prefs, v bool) { p.AutoUpdate.Apply.Set(v) }, }, { - key: syspolicy.EnableRunExitNode, + key: pkey.EnableRunExitNode, get: func(p ipn.PrefsView) bool { return p.AdvertisesExitNode() }, set: func(p *ipn.Prefs, v bool) { p.SetAdvertiseExitNode(v) }, }, @@ -1817,13 +1818,13 @@ var preferencePolicies = []preferencePolicyInfo{ // // b.mu must be held. func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { - if controlURL, err := syspolicy.GetString(syspolicy.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL { + if controlURL, err := syspolicy.GetString(pkey.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL { prefs.ControlURL = controlURL anyChange = true } const sentinel = "HostnameDefaultValue" - hostnameFromPolicy, _ := syspolicy.GetString(syspolicy.Hostname, sentinel) + hostnameFromPolicy, _ := syspolicy.GetString(pkey.Hostname, sentinel) switch hostnameFromPolicy { case sentinel: // An empty string for this policy value means that the admin wants to delete @@ -1858,7 +1859,7 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { anyChange = true } - if alwaysOn, _ := syspolicy.GetBoolean(syspolicy.AlwaysOn, false); alwaysOn && !b.overrideAlwaysOn && !prefs.WantRunning { + if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); alwaysOn && !b.overrideAlwaysOn && !prefs.WantRunning { prefs.WantRunning = true anyChange = true } @@ -1882,7 +1883,7 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { // // b.mu must be held. func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { - if exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, ""); exitNodeIDStr != "" { + if exitNodeIDStr, _ := syspolicy.GetString(pkey.ExitNodeID, ""); exitNodeIDStr != "" { exitNodeID := tailcfg.StableNodeID(exitNodeIDStr) // Try to parse the policy setting value as an "auto:"-prefixed [ipn.ExitNodeExpression], @@ -1923,7 +1924,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange prefs.ExitNodeIP = netip.Addr{} anyChange = true } - } else if exitNodeIPStr, _ := syspolicy.GetString(syspolicy.ExitNodeIP, ""); exitNodeIPStr != "" { + } else if exitNodeIPStr, _ := syspolicy.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" { if prefs.AutoExitNode != "" { prefs.AutoExitNode = "" // mutually exclusive with ExitNodeIP anyChange = true @@ -1970,7 +1971,7 @@ func (b *LocalBackend) reconcilePrefs() (_ ipn.PrefsView, anyChange bool) { // sysPolicyChanged is a callback triggered by syspolicy when it detects // a change in one or more syspolicy settings. func (b *LocalBackend) sysPolicyChanged(policy *rsop.PolicyChange) { - if policy.HasChangedAnyOf(syspolicy.AlwaysOn, syspolicy.AlwaysOnOverrideWithReason) { + if policy.HasChangedAnyOf(pkey.AlwaysOn, pkey.AlwaysOnOverrideWithReason) { // If the AlwaysOn or the AlwaysOnOverrideWithReason policy has changed, // we should reset the overrideAlwaysOn flag, as the override might // no longer be valid. @@ -1979,7 +1980,7 @@ func (b *LocalBackend) sysPolicyChanged(policy *rsop.PolicyChange) { b.mu.Unlock() } - if policy.HasChangedAnyOf(syspolicy.ExitNodeID, syspolicy.ExitNodeIP, syspolicy.AllowExitNodeOverride) { + if policy.HasChangedAnyOf(pkey.ExitNodeID, pkey.ExitNodeIP, pkey.AllowExitNodeOverride) { // Reset the exit node override if a policy that enforces exit node usage // or allows the user to override automatic exit node selection has changed. b.mu.Lock() @@ -1987,7 +1988,7 @@ func (b *LocalBackend) sysPolicyChanged(policy *rsop.PolicyChange) { b.mu.Unlock() } - if policy.HasChanged(syspolicy.AllowedSuggestedExitNodes) { + if policy.HasChanged(pkey.AllowedSuggestedExitNodes) { b.refreshAllowedSuggestions() // Re-evaluate exit node suggestion now that the policy setting has changed. if _, err := b.SuggestExitNode(); err != nil && !errors.Is(err, ErrNoPreferredDERP) { @@ -2348,7 +2349,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { } if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" { - sysak, _ := syspolicy.GetString(syspolicy.AuthKey, "") + sysak, _ := syspolicy.GetString(pkey.AuthKey, "") if sysak != "" { b.logf("Start: setting opts.AuthKey by syspolicy, len=%v", len(sysak)) opts.AuthKey = strings.TrimSpace(sysak) @@ -4407,7 +4408,7 @@ func (b *LocalBackend) checkEditPrefsAccessLocked(actor ipnauth.Actor, prefs ipn // Prevent users from changing exit node preferences // when exit node usage is managed by policy. if mp.ExitNodeIDSet || mp.ExitNodeIPSet || mp.AutoExitNodeSet { - isManaged, err := syspolicy.HasAnyOf(syspolicy.ExitNodeID, syspolicy.ExitNodeIP) + isManaged, err := syspolicy.HasAnyOf(pkey.ExitNodeID, pkey.ExitNodeIP) if err != nil { err = fmt.Errorf("policy check failed: %w", err) } else if isManaged { @@ -4415,7 +4416,7 @@ func (b *LocalBackend) checkEditPrefsAccessLocked(actor ipnauth.Actor, prefs ipn // if permitted by [syspolicy.AllowExitNodeOverride]. // // Disabling exit node usage entirely is not allowed. - allowExitNodeOverride, _ := syspolicy.GetBoolean(syspolicy.AllowExitNodeOverride, false) + allowExitNodeOverride, _ := syspolicy.GetBoolean(pkey.AllowExitNodeOverride, false) if !allowExitNodeOverride || b.changeDisablesExitNodeLocked(prefs, mp) { err = errManagedByPolicy } @@ -4519,7 +4520,7 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o // mode on them until the policy changes, they switch to a different profile, etc. b.overrideAlwaysOn = true - if reconnectAfter, _ := syspolicy.GetDuration(syspolicy.ReconnectAfter, 0); reconnectAfter > 0 { + if reconnectAfter, _ := syspolicy.GetDuration(pkey.ReconnectAfter, 0); reconnectAfter > 0 { b.startReconnectTimerLocked(reconnectAfter) } } @@ -4530,7 +4531,7 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o b.overrideExitNodePolicy = false } if mp.AutoExitNodeSet || mp.ExitNodeIDSet || mp.ExitNodeIPSet { - if allowExitNodeOverride, _ := syspolicy.GetBoolean(syspolicy.AllowExitNodeOverride, false); allowExitNodeOverride { + if allowExitNodeOverride, _ := syspolicy.GetBoolean(pkey.AllowExitNodeOverride, false); allowExitNodeOverride { // If applying exit node policy settings to the new prefs results in no change, // the user is not overriding the policy. Otherwise, it is an override. b.overrideExitNodePolicy = b.applyExitNodeSysPolicyLocked(newPrefs.AsStruct()) @@ -7807,9 +7808,9 @@ type selectRegionFunc func(views.Slice[int]) int type selectNodeFunc func(nodes views.Slice[tailcfg.NodeView], last tailcfg.StableNodeID) tailcfg.NodeView func fillAllowedSuggestions() set.Set[tailcfg.StableNodeID] { - nodes, err := syspolicy.GetStringArray(syspolicy.AllowedSuggestedExitNodes, nil) + nodes, err := syspolicy.GetStringArray(pkey.AllowedSuggestedExitNodes, nil) if err != nil { - log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", syspolicy.AllowedSuggestedExitNodes, err) + log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", pkey.AllowedSuggestedExitNodes, err) return nil } if nodes == nil { @@ -8176,7 +8177,7 @@ func isAllowedAutoExitNodeID(exitNodeID tailcfg.StableNodeID) bool { if exitNodeID == "" { return false // an exit node is required } - if nodes, _ := syspolicy.GetStringArray(syspolicy.AllowedSuggestedExitNodes, nil); nodes != nil { + if nodes, _ := syspolicy.GetStringArray(pkey.AllowedSuggestedExitNodes, nil); nodes != nil { return slices.Contains(nodes, string(exitNodeID)) } @@ -8339,7 +8340,7 @@ func (b *LocalBackend) stateEncrypted() opt.Bool { // the Keychain. A future release will clean up the on-disk state // files. // TODO(#15830): always return true here once MacSys is fully migrated. - sp, _ := syspolicy.GetBoolean(syspolicy.EncryptState, false) + sp, _ := syspolicy.GetBoolean(pkey.EncryptState, false) return opt.NewBool(sp) default: // Probably self-compiled tailscaled, we don't use the Keychain diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 60b5b2c5b..2b83e47f8 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -62,6 +62,7 @@ import ( "tailscale.com/util/must" "tailscale.com/util/set" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/syspolicy/source" "tailscale.com/wgengine" @@ -1182,16 +1183,16 @@ func TestConfigureExitNode(t *testing.T) { // Configure policy settings, if any. store := source.NewTestStore(t) if tt.exitNodeIDPolicy != nil { - store.SetStrings(source.TestSettingOf(syspolicy.ExitNodeID, string(*tt.exitNodeIDPolicy))) + store.SetStrings(source.TestSettingOf(pkey.ExitNodeID, string(*tt.exitNodeIDPolicy))) } if tt.exitNodeIPPolicy != nil { - store.SetStrings(source.TestSettingOf(syspolicy.ExitNodeIP, tt.exitNodeIPPolicy.String())) + store.SetStrings(source.TestSettingOf(pkey.ExitNodeIP, tt.exitNodeIPPolicy.String())) } if tt.exitNodeAllowedIDs != nil { - store.SetStringLists(source.TestSettingOf(syspolicy.AllowedSuggestedExitNodes, toStrings(tt.exitNodeAllowedIDs))) + store.SetStringLists(source.TestSettingOf(pkey.AllowedSuggestedExitNodes, toStrings(tt.exitNodeAllowedIDs))) } if tt.exitNodeAllowOverride { - store.SetBooleans(source.TestSettingOf(syspolicy.AllowExitNodeOverride, true)) + store.SetBooleans(source.TestSettingOf(pkey.AllowExitNodeOverride, true)) } if store.IsEmpty() { // No syspolicy settings, so don't register a store. @@ -2890,10 +2891,10 @@ func TestSetExitNodeIDPolicy(t *testing.T) { policyStore := source.NewTestStore(t) if test.exitNodeIDKey { - policyStore.SetStrings(source.TestSettingOf(syspolicy.ExitNodeID, test.exitNodeID)) + policyStore.SetStrings(source.TestSettingOf(pkey.ExitNodeID, test.exitNodeID)) } if test.exitNodeIPKey { - policyStore.SetStrings(source.TestSettingOf(syspolicy.ExitNodeIP, test.exitNodeIP)) + policyStore.SetStrings(source.TestSettingOf(pkey.ExitNodeIP, test.exitNodeIP)) } syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) @@ -3029,7 +3030,7 @@ func TestUpdateNetmapDeltaAutoExitNode(t *testing.T) { syspolicy.RegisterWellKnownSettingsForTest(t) policyStore := source.NewTestStoreOf(t, source.TestSettingOf( - syspolicy.ExitNodeID, "auto:any", + pkey.ExitNodeID, "auto:any", )) syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) @@ -3114,7 +3115,7 @@ func TestAutoExitNodeSetNetInfoCallback(t *testing.T) { b.cc = cc syspolicy.RegisterWellKnownSettingsForTest(t) policyStore := source.NewTestStoreOf(t, source.TestSettingOf( - syspolicy.ExitNodeID, "auto:any", + pkey.ExitNodeID, "auto:any", )) syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) peer1 := makePeer(1, withCap(26), withDERP(3), withSuggest(), withExitRoutes()) @@ -3223,7 +3224,7 @@ func TestSetControlClientStatusAutoExitNode(t *testing.T) { b := newTestLocalBackend(t) syspolicy.RegisterWellKnownSettingsForTest(t) policyStore := source.NewTestStoreOf(t, source.TestSettingOf( - syspolicy.ExitNodeID, "auto:any", + pkey.ExitNodeID, "auto:any", )) syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) b.currentNode().SetNetMap(nm) @@ -3255,7 +3256,7 @@ func TestApplySysPolicy(t *testing.T) { prefs ipn.Prefs wantPrefs ipn.Prefs wantAnyChange bool - stringPolicies map[syspolicy.Key]string + stringPolicies map[pkey.Key]string }{ { name: "empty prefs without policies", @@ -3290,13 +3291,13 @@ func TestApplySysPolicy(t *testing.T) { RouteAll: true, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ControlURL: "1", - syspolicy.EnableIncomingConnections: "never", - syspolicy.EnableServerMode: "always", - syspolicy.ExitNodeAllowLANAccess: "always", - syspolicy.EnableTailscaleDNS: "always", - syspolicy.EnableTailscaleSubnets: "always", + stringPolicies: map[pkey.Key]string{ + pkey.ControlURL: "1", + pkey.EnableIncomingConnections: "never", + pkey.EnableServerMode: "always", + pkey.ExitNodeAllowLANAccess: "always", + pkey.EnableTailscaleDNS: "always", + pkey.EnableTailscaleSubnets: "always", }, }, { @@ -3311,13 +3312,13 @@ func TestApplySysPolicy(t *testing.T) { ShieldsUp: true, ForceDaemon: true, }, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ControlURL: "1", - syspolicy.EnableIncomingConnections: "never", - syspolicy.EnableServerMode: "always", - syspolicy.ExitNodeAllowLANAccess: "never", - syspolicy.EnableTailscaleDNS: "never", - syspolicy.EnableTailscaleSubnets: "never", + stringPolicies: map[pkey.Key]string{ + pkey.ControlURL: "1", + pkey.EnableIncomingConnections: "never", + pkey.EnableServerMode: "always", + pkey.ExitNodeAllowLANAccess: "never", + pkey.EnableTailscaleDNS: "never", + pkey.EnableTailscaleSubnets: "never", }, }, { @@ -3339,13 +3340,13 @@ func TestApplySysPolicy(t *testing.T) { RouteAll: true, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ControlURL: "2", - syspolicy.EnableIncomingConnections: "always", - syspolicy.EnableServerMode: "never", - syspolicy.ExitNodeAllowLANAccess: "always", - syspolicy.EnableTailscaleDNS: "never", - syspolicy.EnableTailscaleSubnets: "always", + stringPolicies: map[pkey.Key]string{ + pkey.ControlURL: "2", + pkey.EnableIncomingConnections: "always", + pkey.EnableServerMode: "never", + pkey.ExitNodeAllowLANAccess: "always", + pkey.EnableTailscaleDNS: "never", + pkey.EnableTailscaleSubnets: "always", }, }, { @@ -3366,12 +3367,12 @@ func TestApplySysPolicy(t *testing.T) { CorpDNS: true, RouteAll: true, }, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.EnableIncomingConnections: "user-decides", - syspolicy.EnableServerMode: "user-decides", - syspolicy.ExitNodeAllowLANAccess: "user-decides", - syspolicy.EnableTailscaleDNS: "user-decides", - syspolicy.EnableTailscaleSubnets: "user-decides", + stringPolicies: map[pkey.Key]string{ + pkey.EnableIncomingConnections: "user-decides", + pkey.EnableServerMode: "user-decides", + pkey.ExitNodeAllowLANAccess: "user-decides", + pkey.EnableTailscaleDNS: "user-decides", + pkey.EnableTailscaleSubnets: "user-decides", }, }, { @@ -3380,8 +3381,8 @@ func TestApplySysPolicy(t *testing.T) { ControlURL: "set", }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ControlURL: "set", + stringPolicies: map[pkey.Key]string{ + pkey.ControlURL: "set", }, }, { @@ -3399,8 +3400,8 @@ func TestApplySysPolicy(t *testing.T) { }, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ApplyUpdates: "always", + stringPolicies: map[pkey.Key]string{ + pkey.ApplyUpdates: "always", }, }, { @@ -3418,8 +3419,8 @@ func TestApplySysPolicy(t *testing.T) { }, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.ApplyUpdates: "never", + stringPolicies: map[pkey.Key]string{ + pkey.ApplyUpdates: "never", }, }, { @@ -3437,8 +3438,8 @@ func TestApplySysPolicy(t *testing.T) { }, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.CheckUpdates: "always", + stringPolicies: map[pkey.Key]string{ + pkey.CheckUpdates: "always", }, }, { @@ -3456,8 +3457,8 @@ func TestApplySysPolicy(t *testing.T) { }, }, wantAnyChange: true, - stringPolicies: map[syspolicy.Key]string{ - syspolicy.CheckUpdates: "never", + stringPolicies: map[pkey.Key]string{ + pkey.CheckUpdates: "never", }, }, } @@ -5574,7 +5575,7 @@ func TestFillAllowedSuggestions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { policyStore := source.NewTestStoreOf(t, source.TestSettingOf( - syspolicy.AllowedSuggestedExitNodes, tt.allowPolicy, + pkey.AllowedSuggestedExitNodes, tt.allowPolicy, )) syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) @@ -6480,23 +6481,23 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) { }{ { name: "ShieldsUp/True", - stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableIncomingConnections, "never")}, + stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableIncomingConnections, "never")}, want: wantPrefsChanges(fieldChange{"ShieldsUp", true}), }, { name: "ShieldsUp/False", initialPrefs: &ipn.Prefs{ShieldsUp: true}, - stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableIncomingConnections, "always")}, + stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableIncomingConnections, "always")}, want: wantPrefsChanges(fieldChange{"ShieldsUp", false}), }, { name: "ExitNodeID", - stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.ExitNodeID, "foo")}, + stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.ExitNodeID, "foo")}, want: wantPrefsChanges(fieldChange{"ExitNodeID", tailcfg.StableNodeID("foo")}), }, { name: "EnableRunExitNode", - stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableRunExitNode, "always")}, + stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableRunExitNode, "always")}, want: wantPrefsChanges(fieldChange{"AdvertiseRoutes", []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}}), }, { @@ -6505,9 +6506,9 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) { ExitNodeAllowLANAccess: true, }, stringSettings: []source.TestSetting[string]{ - source.TestSettingOf(syspolicy.EnableServerMode, "always"), - source.TestSettingOf(syspolicy.ExitNodeAllowLANAccess, "never"), - source.TestSettingOf(syspolicy.ExitNodeIP, "127.0.0.1"), + source.TestSettingOf(pkey.EnableServerMode, "always"), + source.TestSettingOf(pkey.ExitNodeAllowLANAccess, "never"), + source.TestSettingOf(pkey.ExitNodeIP, "127.0.0.1"), }, want: wantPrefsChanges( fieldChange{"ForceDaemon", true}, @@ -6523,9 +6524,9 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) { AdvertiseRoutes: []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}, }, stringSettings: []source.TestSetting[string]{ - source.TestSettingOf(syspolicy.EnableTailscaleDNS, "always"), - source.TestSettingOf(syspolicy.ExitNodeID, "foo"), - source.TestSettingOf(syspolicy.EnableRunExitNode, "always"), + source.TestSettingOf(pkey.EnableTailscaleDNS, "always"), + source.TestSettingOf(pkey.ExitNodeID, "foo"), + source.TestSettingOf(pkey.EnableRunExitNode, "always"), }, want: nil, // syspolicy settings match the preferences; no change notification is expected. }, diff --git a/ipn/prefs.go b/ipn/prefs.go index 2eb0ccf0c..4c049688c 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -29,6 +29,7 @@ import ( "tailscale.com/types/views" "tailscale.com/util/dnsname" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/version" ) @@ -726,7 +727,7 @@ func (p PrefsView) ControlURLOrDefault() string { // If not configured, or if the configured value is a legacy name equivalent to // the default, then DefaultControlURL is returned instead. func (p *Prefs) ControlURLOrDefault() string { - controlURL, err := syspolicy.GetString(syspolicy.ControlURL, p.ControlURL) + controlURL, err := syspolicy.GetString(pkey.ControlURL, p.ControlURL) if err != nil { controlURL = p.ControlURL } diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index f5c475712..295dc6fff 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -52,6 +52,7 @@ import ( "tailscale.com/util/must" "tailscale.com/util/racebuild" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/testenv" "tailscale.com/version" "tailscale.com/version/distro" @@ -65,7 +66,7 @@ var getLogTargetOnce struct { func getLogTarget() string { getLogTargetOnce.Do(func() { envTarget, _ := os.LookupEnv("TS_LOG_TARGET") - getLogTargetOnce.v, _ = syspolicy.GetString(syspolicy.LogTarget, envTarget) + getLogTargetOnce.v, _ = syspolicy.GetString(pkey.LogTarget, envTarget) }) return getLogTargetOnce.v diff --git a/net/dns/manager_windows.go b/net/dns/manager_windows.go index 6ed5d3ba6..d1cec2a00 100644 --- a/net/dns/manager_windows.go +++ b/net/dns/manager_windows.go @@ -30,6 +30,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/util/dnsname" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/rsop" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/winutil" @@ -508,7 +509,7 @@ func (m *windowsManager) Close() error { // sysPolicyChanged is a callback triggered by [syspolicy] when it detects // a change in one or more syspolicy settings. func (m *windowsManager) sysPolicyChanged(policy *rsop.PolicyChange) { - if policy.HasChanged(syspolicy.EnableDNSRegistration) { + if policy.HasChanged(pkey.EnableDNSRegistration) { m.reconfigureDNSRegistration() } } @@ -520,7 +521,7 @@ func (m *windowsManager) reconfigureDNSRegistration() { // Disable DNS registration by default (if the policy setting is not configured). // This is primarily for historical reasons and to avoid breaking existing // setups that rely on this behavior. - enableDNSRegistration, err := syspolicy.GetPreferenceOptionOrDefault(syspolicy.EnableDNSRegistration, setting.NeverByPolicy) + enableDNSRegistration, err := syspolicy.GetPreferenceOptionOrDefault(pkey.EnableDNSRegistration, setting.NeverByPolicy) if err != nil { m.logf("error getting DNSRegistration policy setting: %v", err) // non-fatal; we'll use the default } diff --git a/posture/serialnumber_syspolicy.go b/posture/serialnumber_syspolicy.go index d6491ff21..5123d561d 100644 --- a/posture/serialnumber_syspolicy.go +++ b/posture/serialnumber_syspolicy.go @@ -10,13 +10,14 @@ import ( "tailscale.com/types/logger" "tailscale.com/util/syspolicy" + "tailscale.com/util/syspolicy/pkey" ) // GetSerialNumbers returns the serial number of the device as reported by an // MDM solution. It requires configuration via the DeviceSerialNumber system policy. // This is the only way to gather serial numbers on iOS, tvOS and Android. func GetSerialNumbers(_ logger.Logf) ([]string, error) { - s, err := syspolicy.GetString(syspolicy.DeviceSerialNumber, "") + s, err := syspolicy.GetString(pkey.DeviceSerialNumber, "") if err != nil { return nil, fmt.Errorf("failed to get serial number from MDM: %v", err) } diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index aea6baf93..bdf90c9a8 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -379,6 +379,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source + tailscale.com/util/syspolicy/pkey from tailscale.com/control/controlclient+ tailscale.com/util/syspolicy/rsop from tailscale.com/ipn/ipnlocal+ tailscale.com/util/syspolicy/setting from tailscale.com/client/local+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index a73c6ebf6..c8a0bb274 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -52,6 +52,7 @@ import ( _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" _ "tailscale.com/util/syspolicy" + _ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/version" _ "tailscale.com/version/distro" _ "tailscale.com/wgengine" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index a73c6ebf6..c8a0bb274 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -52,6 +52,7 @@ import ( _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" _ "tailscale.com/util/syspolicy" + _ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/version" _ "tailscale.com/version/distro" _ "tailscale.com/wgengine" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index a73c6ebf6..c8a0bb274 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -52,6 +52,7 @@ import ( _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" _ "tailscale.com/util/syspolicy" + _ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/version" _ "tailscale.com/version/distro" _ "tailscale.com/wgengine" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index a73c6ebf6..c8a0bb274 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -52,6 +52,7 @@ import ( _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" _ "tailscale.com/util/syspolicy" + _ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/version" _ "tailscale.com/version/distro" _ "tailscale.com/wgengine" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index b5919b962..c9a1cd0cf 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -63,6 +63,7 @@ import ( _ "tailscale.com/util/osdiag" _ "tailscale.com/util/osshare" _ "tailscale.com/util/syspolicy" + _ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/winutil" _ "tailscale.com/util/winutil/gp" _ "tailscale.com/version" diff --git a/util/syspolicy/handler.go b/util/syspolicy/handler.go index c4bfd9de9..cdf32a7f7 100644 --- a/util/syspolicy/handler.go +++ b/util/syspolicy/handler.go @@ -4,6 +4,7 @@ package syspolicy import ( + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/rsop" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/syspolicy/source" @@ -85,22 +86,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/metrics/metrics.go b/util/syspolicy/internal/metrics/metrics.go index 43f2a285a..8f2745673 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 testenv.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), "_") name = strings.ReplaceAll(name, ".", "_") // dots are not allowed in metric names 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..cfef9e17a --- /dev/null +++ b/util/syspolicy/pkey/pkey.go @@ -0,0 +1,177 @@ +// 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 [implicitDefinitions] in util/syspolicy/policy_keys.go. +// 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. + + // AlwaysOn is a boolean key that controls whether Tailscale + // should always remain in a connected state, and the user should + // not be able to disconnect at their discretion. + // + // Warning: This policy setting is experimental and may change or be removed in the future. + // It may also not be fully supported by all Tailscale clients until it is out of experimental status. + // See tailscale/corp#26247, tailscale/corp#26248 and tailscale/corp#26249 for more information. + AlwaysOn Key = "AlwaysOn.Enabled" + + // AlwaysOnOverrideWithReason is a boolean key that alters the behavior + // of [AlwaysOn]. When true, the user is allowed to disconnect Tailscale + // by providing a reason. The reason is logged and sent to the control + // for auditing purposes. It has no effect when [AlwaysOn] is false. + AlwaysOnOverrideWithReason Key = "AlwaysOn.OverrideWithReason" + + // ReconnectAfter is a string value formatted for use with time.ParseDuration() + // that defines the duration after which the client should automatically reconnect + // to the Tailscale network following a user-initiated disconnect. + // An empty string or a zero duration disables automatic reconnection. + ReconnectAfter Key = "ReconnectAfter" + + // 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. + + // AllowExitNodeOverride is a boolean key that allows the user to override exit node policy settings + // and manually select an exit node. It does not allow disabling exit node usage entirely. + // It is typically used in conjunction with [ExitNodeID] set to "auto:any". + // + // Warning: This policy setting is experimental and may change, be renamed or removed in the future. + // It may also not be fully supported by all Tailscale clients until it is out of experimental status. + // See tailscale/corp#29969. + AllowExitNodeOverride Key = "ExitNode.AllowOverride" + + // 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" + + // EnableDNSRegistration is a string value that can be set to "always", "never" + // or "user-decides". It controls whether DNS registration and dynamic DNS + // updates are enabled for the Tailscale interface. For historical reasons + // and to maintain compatibility with existing setups, the default is "never". + // It is only used on Windows. + EnableDNSRegistration Key = "EnableDNSRegistration" + + // 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" + + // EncryptState is a boolean setting that specifies whether to encrypt the + // tailscaled state file with a TPM device. + EncryptState Key = "EncryptState" + + // 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 Android, iOS and tvOS to allow IT administrators to manually give us a serial number via MDM. + // We are unable to programmatically get the serial number on mobile 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 cd5f8172c..e32d9cdf4 100644 --- a/util/syspolicy/policy_keys.go +++ b/util/syspolicy/policy_keys.go @@ -6,225 +6,60 @@ 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. - - // AlwaysOn is a boolean key that controls whether Tailscale - // should always remain in a connected state, and the user should - // not be able to disconnect at their discretion. - // - // Warning: This policy setting is experimental and may change or be removed in the future. - // It may also not be fully supported by all Tailscale clients until it is out of experimental status. - // See tailscale/corp#26247, tailscale/corp#26248 and tailscale/corp#26249 for more information. - AlwaysOn Key = "AlwaysOn.Enabled" - - // AlwaysOnOverrideWithReason is a boolean key that alters the behavior - // of [AlwaysOn]. When true, the user is allowed to disconnect Tailscale - // by providing a reason. The reason is logged and sent to the control - // for auditing purposes. It has no effect when [AlwaysOn] is false. - AlwaysOnOverrideWithReason Key = "AlwaysOn.OverrideWithReason" - - // ReconnectAfter is a string value formatted for use with time.ParseDuration() - // that defines the duration after which the client should automatically reconnect - // to the Tailscale network following a user-initiated disconnect. - // An empty string or a zero duration disables automatic reconnection. - ReconnectAfter Key = "ReconnectAfter" - - // 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. - - // AllowExitNodeOverride is a boolean key that allows the user to override exit node policy settings - // and manually select an exit node. It does not allow disabling exit node usage entirely. - // It is typically used in conjunction with [ExitNodeID] set to "auto:any". - // - // Warning: This policy setting is experimental and may change, be renamed or removed in the future. - // It may also not be fully supported by all Tailscale clients until it is out of experimental status. - // See tailscale/corp#29969. - AllowExitNodeOverride Key = "ExitNode.AllowOverride" - - // 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" - - // EnableDNSRegistration is a string value that can be set to "always", "never" - // or "user-decides". It controls whether DNS registration and dynamic DNS - // updates are enabled for the Tailscale interface. For historical reasons - // and to maintain compatibility with existing setups, the default is "never". - // It is only used on Windows. - EnableDNSRegistration Key = "EnableDNSRegistration" - - // 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" - - // EncryptState is a boolean setting that specifies whether to encrypt the - // tailscaled state file with a TPM device. - EncryptState Key = "EncryptState" - - // 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 Android, iOS and tvOS to allow IT administrators to manually give us a serial number via MDM. - // We are unable to programmatically get the serial number on mobile 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(AllowExitNodeOverride, setting.DeviceSetting, setting.BooleanValue), - setting.NewDefinition(AlwaysOn, setting.DeviceSetting, setting.BooleanValue), - setting.NewDefinition(AlwaysOnOverrideWithReason, setting.DeviceSetting, setting.BooleanValue), - 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(EnableDNSRegistration, setting.DeviceSetting, setting.PreferenceOptionValue), - 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(EncryptState, 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(ReconnectAfter, setting.DeviceSetting, setting.DurationValue), - setting.NewDefinition(Tailnet, setting.DeviceSetting, setting.StringValue), + setting.NewDefinition(pkey.AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue), + setting.NewDefinition(pkey.AllowExitNodeOverride, setting.DeviceSetting, setting.BooleanValue), + setting.NewDefinition(pkey.AlwaysOn, setting.DeviceSetting, setting.BooleanValue), + setting.NewDefinition(pkey.AlwaysOnOverrideWithReason, setting.DeviceSetting, setting.BooleanValue), + 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.EnableDNSRegistration, setting.DeviceSetting, setting.PreferenceOptionValue), + 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.EncryptState, 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.ReconnectAfter, setting.DeviceSetting, setting.DurationValue), + 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() { @@ -248,7 +83,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) }) diff --git a/util/syspolicy/policy_keys_test.go b/util/syspolicy/policy_keys_test.go index 4d3260f3e..490353c81 100644 --- a/util/syspolicy/policy_keys_test.go +++ b/util/syspolicy/policy_keys_test.go @@ -14,14 +14,19 @@ import ( "strconv" "testing" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/setting" ) func TestKnownKeysRegistered(t *testing.T) { - keyConsts, err := listStringConsts[Key]("policy_keys.go") + const file = "pkey/pkey.go" + keyConsts, err := listStringConsts[pkey.Key](file) if err != nil { t.Fatalf("listStringConsts failed: %v", err) } + if len(keyConsts) == 0 { + t.Fatalf("no key constants found in %s", file) + } m, err := setting.DefinitionMapOf(implicitDefinitions) if err != nil { diff --git a/util/syspolicy/rsop/change_callbacks.go b/util/syspolicy/rsop/change_callbacks.go index 87b45b654..59dba07c6 100644 --- a/util/syspolicy/rsop/change_callbacks.go +++ b/util/syspolicy/rsop/change_callbacks.go @@ -11,6 +11,7 @@ import ( "tailscale.com/util/set" "tailscale.com/util/syspolicy/internal/loggerx" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/setting" ) @@ -37,8 +38,8 @@ func (c PolicyChange) Old() *setting.Snapshot { return c.snapshots.Old } -// HasChanged reports whether a policy setting with the specified [setting.Key], has changed. -func (c PolicyChange) HasChanged(key setting.Key) bool { +// HasChanged reports whether a policy setting with the specified [pkey.Key], has changed. +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 { @@ -60,7 +61,7 @@ func (c PolicyChange) HasChanged(key setting.Key) bool { } // HasChangedAnyOf reports whether any of the specified policy settings has changed. -func (c PolicyChange) HasChangedAnyOf(keys ...setting.Key) bool { +func (c PolicyChange) HasChangedAnyOf(keys ...pkey.Key) bool { return slices.ContainsFunc(keys, c.HasChanged) } diff --git a/util/syspolicy/rsop/resultant_policy_test.go b/util/syspolicy/rsop/resultant_policy_test.go index e4bfb1a88..2da46a8ca 100644 --- a/util/syspolicy/rsop/resultant_policy_test.go +++ b/util/syspolicy/rsop/resultant_policy_test.go @@ -15,6 +15,7 @@ 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/setting" "tailscale.com/util/syspolicy/source" @@ -80,7 +81,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 +114,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 +130,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 +160,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 +192,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 +246,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 +264,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 +289,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 +322,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 +348,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 +498,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 { 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 9a96073b0..ea97865f5 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 @@ -169,4 +170,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 13c7a2a5f..9285afade 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" "tailscale.com/util/testenv" ) @@ -134,7 +135,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 @@ -142,12 +143,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 "" } @@ -208,7 +209,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] @@ -224,7 +225,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...)) } @@ -290,7 +291,7 @@ func SetDefinitionsForTest(tb testenv.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..e43495a16 100644 --- a/util/syspolicy/setting/setting_test.go +++ b/util/syspolicy/setting/setting_test.go @@ -11,6 +11,7 @@ import ( "tailscale.com/types/lazy" "tailscale.com/types/ptr" "tailscale.com/util/syspolicy/internal" + "tailscale.com/util/syspolicy/pkey" ) func TestSettingDefinition(t *testing.T) { @@ -18,7 +19,7 @@ func TestSettingDefinition(t *testing.T) { name string setting *Definition osOverride string - wantKey Key + wantKey pkey.Key wantScope Scope wantType Type wantIsSupported bool @@ -163,10 +164,10 @@ func TestSettingDefinition(t *testing.T) { } func TestRegisterSettingDefinition(t *testing.T) { - const testPolicySettingKey Key = "TestPolicySetting" + const testPolicySettingKey pkey.Key = "TestPolicySetting" tests := []struct { name string - key Key + key pkey.Key wantEq *Definition wantErr error }{ diff --git a/util/syspolicy/setting/snapshot.go b/util/syspolicy/setting/snapshot.go index 3a40785dc..94c7ecadb 100644 --- a/util/syspolicy/setting/snapshot.go +++ b/util/syspolicy/setting/snapshot.go @@ -15,34 +15,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 } @@ -50,7 +51,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() @@ -62,7 +63,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 } @@ -94,9 +95,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) } @@ -144,8 +145,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"` } var ( @@ -232,7 +233,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/setting/snapshot_test.go b/util/syspolicy/setting/snapshot_test.go index 19f014aca..99c619cd9 100644 --- a/util/syspolicy/setting/snapshot_test.go +++ b/util/syspolicy/setting/snapshot_test.go @@ -11,6 +11,7 @@ import ( jsonv2 "github.com/go-json-experiment/json" "tailscale.com/util/syspolicy/internal" + "tailscale.com/util/syspolicy/pkey" ) func TestMergeSnapshots(t *testing.T) { @@ -23,23 +24,23 @@ func TestMergeSnapshots(t *testing.T) { name: "both-nil", s1: nil, s2: nil, - want: NewSnapshot(map[Key]RawItem{}), + want: NewSnapshot(map[pkey.Key]RawItem{}), }, { name: "both-empty", - s1: NewSnapshot(map[Key]RawItem{}), - s2: NewSnapshot(map[Key]RawItem{}), - want: NewSnapshot(map[Key]RawItem{}), + s1: NewSnapshot(map[pkey.Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{}), + want: NewSnapshot(map[pkey.Key]RawItem{}), }, { name: "first-nil", s1: nil, - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -47,13 +48,13 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "first-empty", - s1: NewSnapshot(map[Key]RawItem{}), - s2: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -61,13 +62,13 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "second-nil", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }), s2: nil, - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -75,13 +76,13 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "second-empty", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }), - s2: NewSnapshot(map[Key]RawItem{}), - want: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{}), + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -89,17 +90,17 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "no-conflicts", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting4": RawItemOf(2 * time.Hour), "Setting5": RawItemOf(VisibleByPolicy), "Setting6": RawItemOf(ShowChoiceByPolicy), }), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -110,17 +111,17 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "with-conflicts", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(456), "Setting3": RawItemOf(false), "Setting4": RawItemOf(2 * time.Hour), }), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(456), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -129,17 +130,17 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "with-scope-first-wins", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }, DeviceScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(456), "Setting3": RawItemOf(false), "Setting4": RawItemOf(2 * time.Hour), }, CurrentUserScope), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -148,17 +149,17 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "with-scope-second-wins", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }, CurrentUserScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(456), "Setting3": RawItemOf(false), "Setting4": RawItemOf(2 * time.Hour), }, DeviceScope), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(456), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -167,18 +168,18 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "with-scope-both-empty", - s1: NewSnapshot(map[Key]RawItem{}, CurrentUserScope), - s2: NewSnapshot(map[Key]RawItem{}, DeviceScope), - want: NewSnapshot(map[Key]RawItem{}, CurrentUserScope), + s1: NewSnapshot(map[pkey.Key]RawItem{}, CurrentUserScope), + s2: NewSnapshot(map[pkey.Key]RawItem{}, DeviceScope), + want: NewSnapshot(map[pkey.Key]RawItem{}, CurrentUserScope), }, { name: "with-scope-first-empty", - s1: NewSnapshot(map[Key]RawItem{}, CurrentUserScope), - s2: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{}, CurrentUserScope), + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true)}, DeviceScope, NewNamedOrigin("TestPolicy", DeviceScope)), - want: NewSnapshot(map[Key]RawItem{ + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -186,13 +187,13 @@ func TestMergeSnapshots(t *testing.T) { }, { name: "with-scope-second-empty", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), }, CurrentUserScope), - s2: NewSnapshot(map[Key]RawItem{}), - want: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{}), + want: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -226,28 +227,28 @@ func TestSnapshotEqual(t *testing.T) { { name: "nil-empty", s1: nil, - s2: NewSnapshot(map[Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{}), wantEqual: true, wantEqualItems: true, }, { name: "empty-nil", - s1: NewSnapshot(map[Key]RawItem{}), + s1: NewSnapshot(map[pkey.Key]RawItem{}), s2: nil, wantEqual: true, wantEqualItems: true, }, { name: "empty-empty", - s1: NewSnapshot(map[Key]RawItem{}), - s2: NewSnapshot(map[Key]RawItem{}), + s1: NewSnapshot(map[pkey.Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{}), wantEqual: true, wantEqualItems: true, }, { name: "first-nil", s1: nil, - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -257,8 +258,8 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "first-empty", - s1: NewSnapshot(map[Key]RawItem{}), - s2: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -268,7 +269,7 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "second-nil", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(true), @@ -279,23 +280,23 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "second-empty", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }), - s2: NewSnapshot(map[Key]RawItem{}), + s2: NewSnapshot(map[pkey.Key]RawItem{}), wantEqual: false, wantEqualItems: false, }, { name: "same-items-same-order-no-scope", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -305,12 +306,12 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "same-items-same-order-same-scope", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }, DeviceScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -320,12 +321,12 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "same-items-different-order-same-scope", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }, DeviceScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting3": RawItemOf(false), "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), @@ -335,12 +336,12 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "same-items-same-order-different-scope", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }, DeviceScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), @@ -350,12 +351,12 @@ func TestSnapshotEqual(t *testing.T) { }, { name: "different-items-same-scope", - s1: NewSnapshot(map[Key]RawItem{ + s1: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(123), "Setting2": RawItemOf("String"), "Setting3": RawItemOf(false), }, DeviceScope), - s2: NewSnapshot(map[Key]RawItem{ + s2: NewSnapshot(map[pkey.Key]RawItem{ "Setting4": RawItemOf(2 * time.Hour), "Setting5": RawItemOf(VisibleByPolicy), "Setting6": RawItemOf(ShowChoiceByPolicy), @@ -404,7 +405,7 @@ func TestSnapshotString(t *testing.T) { }, { name: "non-empty", - snapshot: NewSnapshot(map[Key]RawItem{ + snapshot: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemOf(2 * time.Hour), "Setting2": RawItemOf(VisibleByPolicy), "Setting3": RawItemOf(ShowChoiceByPolicy), @@ -416,14 +417,14 @@ Setting3 = user-decides`, }, { name: "non-empty-with-item-origin", - snapshot: NewSnapshot(map[Key]RawItem{ + snapshot: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemWith(42, nil, NewNamedOrigin("Test Policy", DeviceScope)), }), wantString: `Setting1 = 42 - {Test Policy (Device)}`, }, { name: "non-empty-with-item-error", - snapshot: NewSnapshot(map[Key]RawItem{ + snapshot: NewSnapshot(map[pkey.Key]RawItem{ "Setting1": RawItemWith(nil, NewErrorText("bang!"), nil), }), wantString: `Setting1 = Error{"bang!"}`, @@ -458,55 +459,55 @@ func TestMarshalUnmarshalSnapshot(t *testing.T) { }, { name: "Bool/True", - snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(true)}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"BoolPolicy": RawItemOf(true)}), wantJSON: `{"Settings": {"BoolPolicy": {"Value": true}}}`, }, { name: "Bool/False", - snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(false)}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"BoolPolicy": RawItemOf(false)}), wantJSON: `{"Settings": {"BoolPolicy": {"Value": false}}}`, }, { name: "String/Non-Empty", - snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("StringValue")}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"StringPolicy": RawItemOf("StringValue")}), wantJSON: `{"Settings": {"StringPolicy": {"Value": "StringValue"}}}`, }, { name: "String/Empty", - snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("")}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"StringPolicy": RawItemOf("")}), wantJSON: `{"Settings": {"StringPolicy": {"Value": ""}}}`, }, { name: "Integer/NonZero", - snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(42))}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"IntPolicy": RawItemOf(uint64(42))}), wantJSON: `{"Settings": {"IntPolicy": {"Value": 42}}}`, }, { name: "Integer/Zero", - snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(0))}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"IntPolicy": RawItemOf(uint64(0))}), wantJSON: `{"Settings": {"IntPolicy": {"Value": 0}}}`, }, { name: "String-List", - snapshot: NewSnapshot(map[Key]RawItem{"ListPolicy": RawItemOf([]string{"Value1", "Value2"})}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"ListPolicy": RawItemOf([]string{"Value1", "Value2"})}), wantJSON: `{"Settings": {"ListPolicy": {"Value": ["Value1", "Value2"]}}}`, }, { name: "Duration/Zero", - snapshot: NewSnapshot(map[Key]RawItem{"DurationPolicy": RawItemOf(time.Duration(0))}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf(time.Duration(0))}), wantJSON: `{"Settings": {"DurationPolicy": {"Value": "0s"}}}`, - wantBack: NewSnapshot(map[Key]RawItem{"DurationPolicy": RawItemOf("0s")}), + wantBack: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf("0s")}), }, { name: "Duration/NonZero", - snapshot: NewSnapshot(map[Key]RawItem{"DurationPolicy": RawItemOf(2 * time.Hour)}), + snapshot: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf(2 * time.Hour)}), wantJSON: `{"Settings": {"DurationPolicy": {"Value": "2h0m0s"}}}`, - wantBack: NewSnapshot(map[Key]RawItem{"DurationPolicy": RawItemOf("2h0m0s")}), + wantBack: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf("2h0m0s")}), }, { name: "Empty/With-Summary", snapshot: NewSnapshot( - map[Key]RawItem{}, + map[pkey.Key]RawItem{}, SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)), ), wantJSON: `{"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"}}`, @@ -514,7 +515,7 @@ func TestMarshalUnmarshalSnapshot(t *testing.T) { { name: "Setting/With-Summary", snapshot: NewSnapshot( - map[Key]RawItem{"PolicySetting": RawItemOf(uint64(42))}, + map[pkey.Key]RawItem{"PolicySetting": RawItemOf(uint64(42))}, SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)), ), wantJSON: `{ @@ -525,7 +526,7 @@ func TestMarshalUnmarshalSnapshot(t *testing.T) { { name: "Settings/With-Origins", snapshot: NewSnapshot( - map[Key]RawItem{ + map[pkey.Key]RawItem{ "SettingA": RawItemWith(uint64(42), nil, NewNamedOrigin("SourceA", DeviceScope)), "SettingB": RawItemWith("B", nil, NewNamedOrigin("SourceB", CurrentProfileScope)), "SettingC": RawItemWith(true, nil, NewNamedOrigin("SourceC", CurrentUserScope)), 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 621701e84..f97b17f3a 100644 --- a/util/syspolicy/source/policy_store_windows.go +++ b/util/syspolicy/source/policy_store_windows.go @@ -13,6 +13,7 @@ import ( "golang.org/x/sys/windows/registry" "tailscale.com/util/set" "tailscale.com/util/syspolicy/internal/loggerx" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/winutil/gp" ) @@ -251,7 +252,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) @@ -261,7 +262,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) @@ -271,7 +272,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) @@ -283,8 +284,8 @@ 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) { +// It returns [pkey.ErrNotConfigured] if the policy setting does not exist. +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) @@ -322,25 +323,25 @@ func (ps *PlatformPolicyStore) ReadStringArray(key setting.Key) ([]string, error }) } -// splitSettingKey extracts the registry key name and value name from a [setting.Key]. -// The [setting.Key] format allows grouping settings into nested categories using one -// or more [setting.KeyPathSeparator]s in the path. How individual policy settings are +// splitSettingKey extracts the registry key name and value name from a [pkey.Key]. +// The [pkey.Key] format allows grouping settings into nested categories using one +// or more [pkey.KeyPathSeparator]s in the path. How individual policy settings are // stored is an implementation detail of each [Store]. In the [PlatformPolicyStore] // for Windows, we map nested policy categories onto the Registry key hierarchy. -// The last component after a [setting.KeyPathSeparator] is treated as the value name, +// The last component after a [pkey.KeyPathSeparator] is treated as the value name, // 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 +// If there are no [pkey.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 efaf4cd5a..ddec9efbb 100644 --- a/util/syspolicy/source/test_store.go +++ b/util/syspolicy/source/test_store.go @@ -12,6 +12,7 @@ import ( "tailscale.com/util/mak" "tailscale.com/util/set" "tailscale.com/util/slicesx" + "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/testenv" ) @@ -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 testenv.TB) *TestStore { - m := make(map[setting.Key]any) + m := make(map[pkey.Key]any) store := &TestStore{ tb: tb, done: make(chan struct{}), @@ -162,7 +163,7 @@ func (s *TestStore) IsEmpty() bool { } // 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() @@ -181,7 +182,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() @@ -200,7 +201,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() @@ -219,7 +220,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() @@ -237,7 +238,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] @@ -399,7 +400,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 6555a58ac..0ac1d2517 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" @@ -58,7 +59,7 @@ func MustRegisterStoreForTest(tb testenv.TB, name string, scope setting.PolicySc // HasAnyOf returns whether at least one of the specified policy settings is configured, // or an error if no keys are provided or the check fails. -func HasAnyOf(keys ...Key) (bool, error) { +func HasAnyOf(keys ...pkey.Key) (bool, error) { if len(keys) == 0 { return false, errors.New("at least one key must be specified") } @@ -82,25 +83,25 @@ func HasAnyOf(keys ...Key) (bool, error) { // 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) } @@ -110,14 +111,14 @@ 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) } // GetPreferenceOptionOrDefault is like [GetPreferenceOption], but allows // specifying a default value to return if the policy setting is not configured. // It can be used in situations where "user-decides" is not the default. -func GetPreferenceOptionOrDefault(name Key, defaultValue setting.PreferenceOption) (setting.PreferenceOption, error) { +func GetPreferenceOptionOrDefault(name pkey.Key, defaultValue setting.PreferenceOption) (setting.PreferenceOption, error) { return getCurrentPolicySettingValue(name, defaultValue) } @@ -126,7 +127,7 @@ func GetPreferenceOptionOrDefault(name Key, defaultValue setting.PreferenceOptio // 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) } @@ -135,7 +136,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 @@ -160,7 +161,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 fc01f3645..5e822a0b7 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" "tailscale.com/util/testenv" @@ -22,7 +23,7 @@ var someOtherError = errors.New("error other than not found") func TestGetString(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue string handlerError error defaultValue string @@ -32,7 +33,7 @@ func TestGetString(t *testing.T) { }{ { name: "read existing value", - key: AdminConsoleVisibility, + key: pkey.AdminConsoleVisibility, handlerValue: "hide", wantValue: "hide", wantMetrics: []metrics.TestState{ @@ -42,13 +43,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", @@ -56,7 +57,7 @@ func TestGetString(t *testing.T) { }, { name: "reading value returns other error", - key: NetworkDevicesVisibility, + key: pkey.NetworkDevicesVisibility, handlerError: someOtherError, wantError: someOtherError, wantMetrics: []metrics.TestState{ @@ -103,7 +104,7 @@ func TestGetString(t *testing.T) { func TestGetUint64(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue uint64 handlerError error defaultValue uint64 @@ -112,27 +113,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, }, @@ -169,7 +170,7 @@ func TestGetUint64(t *testing.T) { func TestGetBoolean(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue bool handlerError error defaultValue bool @@ -179,7 +180,7 @@ func TestGetBoolean(t *testing.T) { }{ { name: "read existing value", - key: FlushDNSOnSessionUnlock, + key: pkey.FlushDNSOnSessionUnlock, handlerValue: true, wantValue: true, wantMetrics: []metrics.TestState{ @@ -189,14 +190,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, @@ -245,7 +246,7 @@ func TestGetBoolean(t *testing.T) { func TestGetPreferenceOption(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue string handlerError error wantValue setting.PreferenceOption @@ -254,7 +255,7 @@ func TestGetPreferenceOption(t *testing.T) { }{ { name: "always by policy", - key: EnableIncomingConnections, + key: pkey.EnableIncomingConnections, handlerValue: "always", wantValue: setting.AlwaysByPolicy, wantMetrics: []metrics.TestState{ @@ -264,7 +265,7 @@ func TestGetPreferenceOption(t *testing.T) { }, { name: "never by policy", - key: EnableIncomingConnections, + key: pkey.EnableIncomingConnections, handlerValue: "never", wantValue: setting.NeverByPolicy, wantMetrics: []metrics.TestState{ @@ -274,7 +275,7 @@ func TestGetPreferenceOption(t *testing.T) { }, { name: "use default", - key: EnableIncomingConnections, + key: pkey.EnableIncomingConnections, handlerValue: "", wantValue: setting.ShowChoiceByPolicy, wantMetrics: []metrics.TestState{ @@ -284,13 +285,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, @@ -338,7 +339,7 @@ func TestGetPreferenceOption(t *testing.T) { func TestGetVisibility(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue string handlerError error wantValue setting.Visibility @@ -347,7 +348,7 @@ func TestGetVisibility(t *testing.T) { }{ { name: "hidden by policy", - key: AdminConsoleVisibility, + key: pkey.AdminConsoleVisibility, handlerValue: "hide", wantValue: setting.HiddenByPolicy, wantMetrics: []metrics.TestState{ @@ -357,7 +358,7 @@ func TestGetVisibility(t *testing.T) { }, { name: "visibility default", - key: AdminConsoleVisibility, + key: pkey.AdminConsoleVisibility, handlerValue: "show", wantValue: setting.VisibleByPolicy, wantMetrics: []metrics.TestState{ @@ -367,14 +368,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, @@ -423,7 +424,7 @@ func TestGetVisibility(t *testing.T) { func TestGetDuration(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue string handlerError error defaultValue time.Duration @@ -433,7 +434,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, @@ -444,7 +445,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"`), @@ -456,21 +457,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, @@ -519,7 +520,7 @@ func TestGetDuration(t *testing.T) { func TestGetStringArray(t *testing.T) { tests := []struct { name string - key Key + key pkey.Key handlerValue []string handlerError error defaultValue []string @@ -529,7 +530,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{ @@ -539,13 +540,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"}, @@ -553,7 +554,7 @@ func TestGetStringArray(t *testing.T) { }, { name: "reading value returns other error", - key: AllowedSuggestedExitNodes, + key: pkey.AllowedSuggestedExitNodes, handlerError: someOtherError, wantError: someOtherError, wantMetrics: []metrics.TestState{ @@ -607,11 +608,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) }