From 1ca4ae598a8369c53f91eec09e19c7f2326ed539 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 1 Sep 2025 15:05:06 -0700 Subject: [PATCH] ipn/ipnlocal: use policyclient.Client always, stop using global syspolicy funcs Step 4 of N. See earlier commits in the series (via the issue) for the plan. This adds the missing methods to policyclient.Client and then uses it everywhere in ipn/ipnlocal and locks it in with a new dep test. Still plenty of users of the global syspolicy elsewhere in the tree, but this is a lot of them. Updates #16998 Updates #12614 Change-Id: I25b136539ae1eedbcba80124de842970db0ca314 Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/c2n.go | 3 +- ipn/ipnlocal/local.go | 71 ++++++++++----------- ipn/ipnlocal/local_test.go | 50 +++++++++++++-- tsd/syspolicy_on.go | 23 +++++++ tstest/deptest/deptest.go | 12 +++- util/syspolicy/policyclient/policyclient.go | 56 +++++++++++++++- 6 files changed, 168 insertions(+), 47 deletions(-) diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index b1a780cc1..339fad50a 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -29,7 +29,6 @@ import ( "tailscale.com/util/clientmetric" "tailscale.com/util/goroutines" "tailscale.com/util/set" - "tailscale.com/util/syspolicy" "tailscale.com/util/syspolicy/pkey" "tailscale.com/version" "tailscale.com/version/distro" @@ -343,7 +342,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(pkey.PostureChecking) + choice, err := b.polc.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 61bde31e4..5f70ae8ef 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -107,7 +107,6 @@ import ( "tailscale.com/util/rands" "tailscale.com/util/set" "tailscale.com/util/slicesx" - "tailscale.com/util/syspolicy" "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/systemd" @@ -382,7 +381,7 @@ type LocalBackend struct { lastSuggestedExitNode tailcfg.StableNodeID // allowedSuggestedExitNodes is a set of exit nodes permitted by the most recent - // [syspolicy.AllowedSuggestedExitNodes] value. The allowedSuggestedExitNodesMu + // [pkey.AllowedSuggestedExitNodes] value. The allowedSuggestedExitNodesMu // mutex guards access to this set. allowedSuggestedExitNodesMu sync.Mutex allowedSuggestedExitNodes set.Set[tailcfg.StableNodeID] @@ -405,10 +404,10 @@ type LocalBackend struct { // (sending false). needsCaptiveDetection chan bool - // overrideAlwaysOn is whether [syspolicy.AlwaysOn] is overridden by the user + // overrideAlwaysOn is whether [pkey.AlwaysOn] is overridden by the user // and should have no impact on the WantRunning state until the policy changes, // or the user re-connects manually, switches to a different profile, etc. - // Notably, this is true when [syspolicy.AlwaysOnOverrideWithReason] is enabled, + // Notably, this is true when [pkey.AlwaysOnOverrideWithReason] is enabled, // and the user has disconnected with a reason. // See tailscale/corp#26146. overrideAlwaysOn bool @@ -418,9 +417,9 @@ type LocalBackend struct { reconnectTimer tstime.TimerController // overrideExitNodePolicy is whether the user has overridden the exit node policy - // by manually selecting an exit node, as allowed by [syspolicy.AllowExitNodeOverride]. + // by manually selecting an exit node, as allowed by [pkey.AllowExitNodeOverride]. // - // If true, the [syspolicy.ExitNodeID] and [syspolicy.ExitNodeIP] policy settings are ignored, + // If true, the [pkey.ExitNodeID] and [pkey.ExitNodeIP] policy settings are ignored, // and the suggested exit node is not applied automatically. // // It is cleared when the user switches back to the state required by policy (typically, auto:any), @@ -679,7 +678,7 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim } } case "syspolicy": - setEnabled = syspolicy.SetDebugLoggingEnabled + setEnabled = b.polc.SetDebugLoggingEnabled } if setEnabled == nil || !slices.Contains(ipn.DebuggableComponents, component) { return fmt.Errorf("unknown component %q", component) @@ -1820,13 +1819,13 @@ var preferencePolicies = []preferencePolicyInfo{ // // b.mu must be held. func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { - if controlURL, err := syspolicy.GetString(pkey.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL { + if controlURL, err := b.polc.GetString(pkey.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL { prefs.ControlURL = controlURL anyChange = true } const sentinel = "HostnameDefaultValue" - hostnameFromPolicy, _ := syspolicy.GetString(pkey.Hostname, sentinel) + hostnameFromPolicy, _ := b.polc.GetString(pkey.Hostname, sentinel) switch hostnameFromPolicy { case sentinel: // An empty string for this policy value means that the admin wants to delete @@ -1861,13 +1860,13 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) { anyChange = true } - if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); alwaysOn && !b.overrideAlwaysOn && !prefs.WantRunning { + if alwaysOn, _ := b.polc.GetBoolean(pkey.AlwaysOn, false); alwaysOn && !b.overrideAlwaysOn && !prefs.WantRunning { prefs.WantRunning = true anyChange = true } for _, opt := range preferencePolicies { - if po, err := syspolicy.GetPreferenceOption(opt.key); err == nil { + if po, err := b.polc.GetPreferenceOption(opt.key); err == nil { curVal := opt.get(prefs.View()) newVal := po.ShouldEnable(curVal) if curVal != newVal { @@ -1885,7 +1884,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(pkey.ExitNodeID, ""); exitNodeIDStr != "" { + if exitNodeIDStr, _ := b.polc.GetString(pkey.ExitNodeID, ""); exitNodeIDStr != "" { exitNodeID := tailcfg.StableNodeID(exitNodeIDStr) // Try to parse the policy setting value as an "auto:"-prefixed [ipn.ExitNodeExpression], @@ -1914,7 +1913,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange // or requires an auto exit node ID and the current one isn't allowed, // then update the exit node ID. if prefs.ExitNodeID != exitNodeID { - if !useAutoExitNode || !isAllowedAutoExitNodeID(prefs.ExitNodeID) { + if !useAutoExitNode || !isAllowedAutoExitNodeID(b.polc, prefs.ExitNodeID) { prefs.ExitNodeID = exitNodeID anyChange = true } @@ -1926,7 +1925,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange prefs.ExitNodeIP = netip.Addr{} anyChange = true } - } else if exitNodeIPStr, _ := syspolicy.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" { + } else if exitNodeIPStr, _ := b.polc.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" { if prefs.AutoExitNode != "" { prefs.AutoExitNode = "" // mutually exclusive with ExitNodeIP anyChange = true @@ -1946,7 +1945,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange // registerSysPolicyWatch subscribes to syspolicy change notifications // and immediately applies the effective syspolicy settings to the current profile. func (b *LocalBackend) registerSysPolicyWatch() (unregister func(), err error) { - if unregister, err = syspolicy.RegisterChangeCallback(b.sysPolicyChanged); err != nil { + if unregister, err = b.polc.RegisterChangeCallback(b.sysPolicyChanged); err != nil { return nil, fmt.Errorf("syspolicy: LocalBacked failed to register policy change callback: %v", err) } if prefs, anyChange := b.reconcilePrefs(); anyChange { @@ -1996,7 +1995,7 @@ func (b *LocalBackend) sysPolicyChanged(policy policyclient.PolicyChange) { if _, err := b.SuggestExitNode(); err != nil && !errors.Is(err, ErrNoPreferredDERP) { b.logf("failed to select auto exit node: %v", err) } - // If [syspolicy.ExitNodeID] is set to `auto:any`, the suggested exit node ID + // If [pkey.ExitNodeID] is set to `auto:any`, the suggested exit node ID // will be used when [applySysPolicy] updates the current profile's prefs. } @@ -2132,7 +2131,7 @@ func (b *LocalBackend) resolveAutoExitNodeLocked(prefs *ipn.Prefs) (prefsChanged if !b.lastSuggestedExitNode.IsZero() { // If we have a suggested exit node, use it. newExitNodeID = b.lastSuggestedExitNode - } else if isAllowedAutoExitNodeID(prefs.ExitNodeID) { + } else if isAllowedAutoExitNodeID(b.polc, prefs.ExitNodeID) { // If we don't have a suggested exit node, but the prefs already // specify an allowed auto exit node ID, retain it. newExitNodeID = prefs.ExitNodeID @@ -2351,7 +2350,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { } if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" { - sysak, _ := syspolicy.GetString(pkey.AuthKey, "") + sysak, _ := b.polc.GetString(pkey.AuthKey, "") if sysak != "" { b.logf("Start: setting opts.AuthKey by syspolicy, len=%v", len(sysak)) opts.AuthKey = strings.TrimSpace(sysak) @@ -4111,7 +4110,7 @@ func (b *LocalBackend) resolveBestProfileLocked() (_ ipn.LoginProfileView, isBac if b.currentUser != nil { profile := b.pm.CurrentProfile() // TODO(nickkhyl): check if the current profile is allowed on the device, - // such as when [syspolicy.Tailnet] policy setting requires a specific Tailnet. + // such as when [pkey.Tailnet] policy setting requires a specific Tailnet. // See tailscale/corp#26249. if uid := b.currentUser.UserID(); profile.LocalUserID() != uid { profile = b.pm.DefaultUserProfile(uid) @@ -4138,7 +4137,7 @@ func (b *LocalBackend) resolveBestProfileLocked() (_ ipn.LoginProfileView, isBac // using the current profile. // // TODO(nickkhyl): check if the current profile is allowed on the device, - // such as when [syspolicy.Tailnet] policy setting requires a specific Tailnet. + // such as when [pkey.Tailnet] policy setting requires a specific Tailnet. // See tailscale/corp#26249. return b.pm.CurrentProfile(), false } @@ -4411,15 +4410,15 @@ 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(pkey.ExitNodeID, pkey.ExitNodeIP) + isManaged, err := b.polc.HasAnyOf(pkey.ExitNodeID, pkey.ExitNodeIP) if err != nil { err = fmt.Errorf("policy check failed: %w", err) } else if isManaged { // Allow users to override ExitNode policy settings and select an exit node manually - // if permitted by [syspolicy.AllowExitNodeOverride]. + // if permitted by [pkey.AllowExitNodeOverride]. // // Disabling exit node usage entirely is not allowed. - allowExitNodeOverride, _ := syspolicy.GetBoolean(pkey.AllowExitNodeOverride, false) + allowExitNodeOverride, _ := b.polc.GetBoolean(pkey.AllowExitNodeOverride, false) if !allowExitNodeOverride || b.changeDisablesExitNodeLocked(prefs, mp) { err = errManagedByPolicy } @@ -4517,13 +4516,13 @@ func (b *LocalBackend) adjustEditPrefsLocked(prefs ipn.PrefsView, mp *ipn.Masked // b.mu must be held; mp must not be mutated by this method. func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, oldPrefs, newPrefs ipn.PrefsView) { if mp.WantRunningSet && !mp.WantRunning && oldPrefs.WantRunning() { - // If a user has enough rights to disconnect, such as when [syspolicy.AlwaysOn] - // is disabled, or [syspolicy.AlwaysOnOverrideWithReason] is also set and the user + // If a user has enough rights to disconnect, such as when [pkey.AlwaysOn] + // is disabled, or [pkey.AlwaysOnOverrideWithReason] is also set and the user // provides a reason for disconnecting, then we should not force the "always on" // mode on them until the policy changes, they switch to a different profile, etc. b.overrideAlwaysOn = true - if reconnectAfter, _ := syspolicy.GetDuration(pkey.ReconnectAfter, 0); reconnectAfter > 0 { + if reconnectAfter, _ := b.polc.GetDuration(pkey.ReconnectAfter, 0); reconnectAfter > 0 { b.startReconnectTimerLocked(reconnectAfter) } } @@ -4534,7 +4533,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(pkey.AllowExitNodeOverride, false); allowExitNodeOverride { + if allowExitNodeOverride, _ := b.polc.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()) @@ -5643,7 +5642,7 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip // was selected, if any. // // If auto exit node is enabled (via [ipn.Prefs.AutoExitNode] or - // [syspolicy.ExitNodeID]), or an exit node is specified by ExitNodeIP + // [pkey.ExitNodeID]), or an exit node is specified by ExitNodeIP // instead of ExitNodeID , and we don't yet have enough info to resolve // it (usually due to missing netmap or net report), then ExitNodeID in // the prefs may be invalid (typically, [unresolvedExitNodeID]) until @@ -7786,7 +7785,7 @@ func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionRes } // getAllowedSuggestions returns a set of exit nodes permitted by the most recent -// [syspolicy.AllowedSuggestedExitNodes] value. Callers must not mutate the returned set. +// [pkey.AllowedSuggestedExitNodes] value. Callers must not mutate the returned set. func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] { b.allowedSuggestedExitNodesMu.Lock() defer b.allowedSuggestedExitNodesMu.Unlock() @@ -7794,11 +7793,11 @@ func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] { } // refreshAllowedSuggestions rebuilds the set of permitted exit nodes -// from the current [syspolicy.AllowedSuggestedExitNodes] value. +// from the current [pkey.AllowedSuggestedExitNodes] value. func (b *LocalBackend) refreshAllowedSuggestions() { b.allowedSuggestedExitNodesMu.Lock() defer b.allowedSuggestedExitNodesMu.Unlock() - b.allowedSuggestedExitNodes = fillAllowedSuggestions() + b.allowedSuggestedExitNodes = fillAllowedSuggestions(b.polc) } // selectRegionFunc returns a DERP region from the slice of candidate regions. @@ -7810,8 +7809,8 @@ type selectRegionFunc func(views.Slice[int]) int // choice. type selectNodeFunc func(nodes views.Slice[tailcfg.NodeView], last tailcfg.StableNodeID) tailcfg.NodeView -func fillAllowedSuggestions() set.Set[tailcfg.StableNodeID] { - nodes, err := syspolicy.GetStringArray(pkey.AllowedSuggestedExitNodes, nil) +func fillAllowedSuggestions(polc policyclient.Client) set.Set[tailcfg.StableNodeID] { + nodes, err := polc.GetStringArray(pkey.AllowedSuggestedExitNodes, nil) if err != nil { log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", pkey.AllowedSuggestedExitNodes, err) return nil @@ -8176,11 +8175,11 @@ const ( unresolvedExitNodeID tailcfg.StableNodeID = "auto:any" ) -func isAllowedAutoExitNodeID(exitNodeID tailcfg.StableNodeID) bool { +func isAllowedAutoExitNodeID(polc policyclient.Client, exitNodeID tailcfg.StableNodeID) bool { if exitNodeID == "" { return false // an exit node is required } - if nodes, _ := syspolicy.GetStringArray(pkey.AllowedSuggestedExitNodes, nil); nodes != nil { + if nodes, _ := polc.GetStringArray(pkey.AllowedSuggestedExitNodes, nil); nodes != nil { return slices.Contains(nodes, string(exitNodeID)) } @@ -8343,7 +8342,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(pkey.EncryptState, false) + sp, _ := b.polc.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 2b83e47f8..0967bf1ff 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -47,6 +47,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/tsd" "tailscale.com/tstest" + "tailscale.com/tstest/deptest" "tailscale.com/types/dnstype" "tailscale.com/types/ipproto" "tailscale.com/types/key" @@ -63,6 +64,7 @@ import ( "tailscale.com/util/set" "tailscale.com/util/syspolicy" "tailscale.com/util/syspolicy/pkey" + "tailscale.com/util/syspolicy/policyclient" "tailscale.com/util/syspolicy/setting" "tailscale.com/util/syspolicy/source" "tailscale.com/wgengine" @@ -5541,6 +5543,28 @@ func TestReadWriteRouteInfo(t *testing.T) { } } +// staticPolicy maps policy keys to their corresponding values, +// which must be of the correct type (string, []string, bool, etc). +// +// It is used for testing purposes to simulate policy client behavior. +// It panics if the values are the wrong type. +type staticPolicy map[pkey.Key]any + +type testPolicy struct { + staticPolicy + policyclient.Client +} + +func (sp testPolicy) GetStringArray(key pkey.Key, defaultVal []string) ([]string, error) { + if val, ok := sp.staticPolicy[key]; ok { + if arr, ok := val.([]string); ok { + return arr, nil + } + return nil, fmt.Errorf("key %s is not a []string", key) + } + return defaultVal, nil +} + func TestFillAllowedSuggestions(t *testing.T) { tests := []struct { name string @@ -5571,15 +5595,16 @@ func TestFillAllowedSuggestions(t *testing.T) { want: []tailcfg.StableNodeID{"ABC", "def", "gHiJ"}, }, } - syspolicy.RegisterWellKnownSettingsForTest(t) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - policyStore := source.NewTestStoreOf(t, source.TestSettingOf( - pkey.AllowedSuggestedExitNodes, tt.allowPolicy, - )) - syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore) + polc := testPolicy{ + staticPolicy: staticPolicy{ + pkey.AllowedSuggestedExitNodes: tt.allowPolicy, + }, + } - got := fillAllowedSuggestions() + got := fillAllowedSuggestions(polc) if got == nil { if tt.want == nil { return @@ -7008,6 +7033,19 @@ func TestDisplayMessageIPNBus(t *testing.T) { } } +func TestDeps(t *testing.T) { + deptest.DepChecker{ + OnImport: func(pkg string) { + switch pkg { + case "tailscale.com/util/syspolicy", + "tailscale.com/util/syspolicy/setting", + "tailscale.com/util/syspolicy/rsop": + t.Errorf("ipn/ipnlocal: importing syspolicy package %q is not allowed; only policyclient and its deps should be used by ipn/ipnlocal", pkg) + } + }, + }.Check(t) +} + func checkError(tb testing.TB, got, want error, fatal bool) { tb.Helper() f := tb.Errorf diff --git a/tsd/syspolicy_on.go b/tsd/syspolicy_on.go index 8d7762bd9..e9811b88b 100644 --- a/tsd/syspolicy_on.go +++ b/tsd/syspolicy_on.go @@ -6,9 +6,12 @@ package tsd import ( + "time" + "tailscale.com/util/syspolicy" "tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/policyclient" + "tailscale.com/util/syspolicy/ptype" ) func getPolicyClient() policyclient.Client { return globalSyspolicy{} } @@ -36,6 +39,26 @@ func (globalSyspolicy) SetDebugLoggingEnabled(enabled bool) { syspolicy.SetDebugLoggingEnabled(enabled) } +func (globalSyspolicy) GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) { + return syspolicy.GetUint64(key, defaultValue) +} + +func (globalSyspolicy) GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) { + return syspolicy.GetDuration(name, defaultValue) +} + +func (globalSyspolicy) GetPreferenceOption(name pkey.Key) (ptype.PreferenceOption, error) { + return syspolicy.GetPreferenceOption(name) +} + +func (globalSyspolicy) GetVisibility(name pkey.Key) (ptype.Visibility, error) { + return syspolicy.GetVisibility(name) +} + +func (globalSyspolicy) HasAnyOf(keys ...pkey.Key) (bool, error) { + return syspolicy.HasAnyOf(keys...) +} + func (globalSyspolicy) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) { return syspolicy.RegisterChangeCallback(cb) } diff --git a/tstest/deptest/deptest.go b/tstest/deptest/deptest.go index c248d6c20..c0b6d8b8c 100644 --- a/tstest/deptest/deptest.go +++ b/tstest/deptest/deptest.go @@ -24,7 +24,8 @@ import ( type DepChecker struct { GOOS string // optional GOARCH string // optional - OnDep func(string) // if non-nil, called per import + OnDep func(string) // if non-nil, called per dependency + OnImport func(string) // if non-nil, called per import BadDeps map[string]string // package => why WantDeps set.Set[string] // packages expected Tags string // comma-separated @@ -52,7 +53,8 @@ func (c DepChecker) Check(t *testing.T) { t.Fatal(err) } var res struct { - Deps []string + Imports []string + Deps []string } if err := json.Unmarshal(out, &res); err != nil { t.Fatal(err) @@ -66,6 +68,12 @@ func (c DepChecker) Check(t *testing.T) { return strings.TrimSpace(string(out)) }) + if c.OnImport != nil { + for _, imp := range res.Imports { + c.OnImport(imp) + } + } + for _, dep := range res.Deps { if c.OnDep != nil { c.OnDep(dep) diff --git a/util/syspolicy/policyclient/policyclient.go b/util/syspolicy/policyclient/policyclient.go index 0b15599c1..aadcbc60e 100644 --- a/util/syspolicy/policyclient/policyclient.go +++ b/util/syspolicy/policyclient/policyclient.go @@ -6,7 +6,12 @@ // of syspolicy is omitted from the build. package policyclient -import "tailscale.com/util/syspolicy/pkey" +import ( + "time" + + "tailscale.com/util/syspolicy/pkey" + "tailscale.com/util/syspolicy/ptype" +) // Client is the interface between code making questions about the system policy // and the actual implementation. @@ -23,9 +28,38 @@ type Client interface { // or defaultValue (and a nil error) if it does not exist. GetBoolean(key pkey.Key, defaultValue bool) (bool, error) + // GetUint64 returns a numeric policy setting with the specified key, + // or defaultValue (and a nil error) if it does not exist. + GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) + + // GetDuration loads a policy from the registry that can be managed by an + // enterprise policy management system and describes a duration for some + // action. The registry value should be a string that time.ParseDuration + // understands. If the registry value is "" or can not be processed, + // defaultValue (and a nil error) is returned instead. + GetDuration(key pkey.Key, defaultValue time.Duration) (time.Duration, error) + + // GetPreferenceOption loads a policy from the registry that can be + // managed by an enterprise policy management system and allows administrative + // overrides of users' choices in a way that we do not want tailcontrol to have + // 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. + GetPreferenceOption(key pkey.Key) (ptype.PreferenceOption, error) + + // GetVisibility returns whether a UI element should be visible based on + // the system's configuration. + // If unconfigured, implementations should return [ptype.VisibleByPolicy] + // and a nil error. + GetVisibility(key pkey.Key) (ptype.Visibility, error) + // SetDebugLoggingEnabled enables or disables debug logging for the policy client. SetDebugLoggingEnabled(enabled bool) + // 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. + HasAnyOf(keys ...pkey.Key) (bool, error) + // RegisterChangeCallback registers a callback function that will be called // whenever a policy change is detected. It returns a function to unregister // the callback and an error if the registration fails. @@ -59,6 +93,26 @@ func (NoPolicyClient) GetStringArray(key pkey.Key, defaultValue []string) ([]str return defaultValue, nil } +func (NoPolicyClient) GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) { + return defaultValue, nil +} + +func (NoPolicyClient) GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) { + return defaultValue, nil +} + +func (NoPolicyClient) GetPreferenceOption(name pkey.Key) (ptype.PreferenceOption, error) { + return ptype.ShowChoiceByPolicy, nil +} + +func (NoPolicyClient) GetVisibility(name pkey.Key) (ptype.Visibility, error) { + return ptype.VisibleByPolicy, nil +} + +func (NoPolicyClient) HasAnyOf(keys ...pkey.Key) (bool, error) { + return false, nil +} + func (NoPolicyClient) SetDebugLoggingEnabled(enabled bool) {} func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {