mirror of
https://github.com/tailscale/tailscale.git
synced 2025-09-21 05:31:36 +02:00
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 <bradfitz@tailscale.com>
This commit is contained in:
parent
2434bc69fc
commit
1ca4ae598a
@ -29,7 +29,6 @@ import (
|
|||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/goroutines"
|
"tailscale.com/util/goroutines"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
"tailscale.com/util/syspolicy"
|
|
||||||
"tailscale.com/util/syspolicy/pkey"
|
"tailscale.com/util/syspolicy/pkey"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/version/distro"
|
"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
|
// this will first check syspolicy, MDM settings like Registry
|
||||||
// on Windows or defaults on macOS. If they are not set, it falls
|
// on Windows or defaults on macOS. If they are not set, it falls
|
||||||
// back to the cli-flag, `--posture-checking`.
|
// back to the cli-flag, `--posture-checking`.
|
||||||
choice, err := syspolicy.GetPreferenceOption(pkey.PostureChecking)
|
choice, err := b.polc.GetPreferenceOption(pkey.PostureChecking)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logf(
|
b.logf(
|
||||||
"c2n: failed to read PostureChecking from syspolicy, returning default from CLI: %s; got error: %s",
|
"c2n: failed to read PostureChecking from syspolicy, returning default from CLI: %s; got error: %s",
|
||||||
|
@ -107,7 +107,6 @@ import (
|
|||||||
"tailscale.com/util/rands"
|
"tailscale.com/util/rands"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
"tailscale.com/util/slicesx"
|
"tailscale.com/util/slicesx"
|
||||||
"tailscale.com/util/syspolicy"
|
|
||||||
"tailscale.com/util/syspolicy/pkey"
|
"tailscale.com/util/syspolicy/pkey"
|
||||||
"tailscale.com/util/syspolicy/policyclient"
|
"tailscale.com/util/syspolicy/policyclient"
|
||||||
"tailscale.com/util/systemd"
|
"tailscale.com/util/systemd"
|
||||||
@ -382,7 +381,7 @@ type LocalBackend struct {
|
|||||||
lastSuggestedExitNode tailcfg.StableNodeID
|
lastSuggestedExitNode tailcfg.StableNodeID
|
||||||
|
|
||||||
// allowedSuggestedExitNodes is a set of exit nodes permitted by the most recent
|
// 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.
|
// mutex guards access to this set.
|
||||||
allowedSuggestedExitNodesMu sync.Mutex
|
allowedSuggestedExitNodesMu sync.Mutex
|
||||||
allowedSuggestedExitNodes set.Set[tailcfg.StableNodeID]
|
allowedSuggestedExitNodes set.Set[tailcfg.StableNodeID]
|
||||||
@ -405,10 +404,10 @@ type LocalBackend struct {
|
|||||||
// (sending false).
|
// (sending false).
|
||||||
needsCaptiveDetection chan bool
|
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,
|
// 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.
|
// 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.
|
// and the user has disconnected with a reason.
|
||||||
// See tailscale/corp#26146.
|
// See tailscale/corp#26146.
|
||||||
overrideAlwaysOn bool
|
overrideAlwaysOn bool
|
||||||
@ -418,9 +417,9 @@ type LocalBackend struct {
|
|||||||
reconnectTimer tstime.TimerController
|
reconnectTimer tstime.TimerController
|
||||||
|
|
||||||
// overrideExitNodePolicy is whether the user has overridden the exit node policy
|
// 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.
|
// 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),
|
// 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":
|
case "syspolicy":
|
||||||
setEnabled = syspolicy.SetDebugLoggingEnabled
|
setEnabled = b.polc.SetDebugLoggingEnabled
|
||||||
}
|
}
|
||||||
if setEnabled == nil || !slices.Contains(ipn.DebuggableComponents, component) {
|
if setEnabled == nil || !slices.Contains(ipn.DebuggableComponents, component) {
|
||||||
return fmt.Errorf("unknown component %q", component)
|
return fmt.Errorf("unknown component %q", component)
|
||||||
@ -1820,13 +1819,13 @@ var preferencePolicies = []preferencePolicyInfo{
|
|||||||
//
|
//
|
||||||
// b.mu must be held.
|
// b.mu must be held.
|
||||||
func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
|
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
|
prefs.ControlURL = controlURL
|
||||||
anyChange = true
|
anyChange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const sentinel = "HostnameDefaultValue"
|
const sentinel = "HostnameDefaultValue"
|
||||||
hostnameFromPolicy, _ := syspolicy.GetString(pkey.Hostname, sentinel)
|
hostnameFromPolicy, _ := b.polc.GetString(pkey.Hostname, sentinel)
|
||||||
switch hostnameFromPolicy {
|
switch hostnameFromPolicy {
|
||||||
case sentinel:
|
case sentinel:
|
||||||
// An empty string for this policy value means that the admin wants to delete
|
// 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
|
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
|
prefs.WantRunning = true
|
||||||
anyChange = true
|
anyChange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range preferencePolicies {
|
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())
|
curVal := opt.get(prefs.View())
|
||||||
newVal := po.ShouldEnable(curVal)
|
newVal := po.ShouldEnable(curVal)
|
||||||
if curVal != newVal {
|
if curVal != newVal {
|
||||||
@ -1885,7 +1884,7 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
|
|||||||
//
|
//
|
||||||
// b.mu must be held.
|
// b.mu must be held.
|
||||||
func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
|
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)
|
exitNodeID := tailcfg.StableNodeID(exitNodeIDStr)
|
||||||
|
|
||||||
// Try to parse the policy setting value as an "auto:"-prefixed [ipn.ExitNodeExpression],
|
// 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,
|
// or requires an auto exit node ID and the current one isn't allowed,
|
||||||
// then update the exit node ID.
|
// then update the exit node ID.
|
||||||
if prefs.ExitNodeID != exitNodeID {
|
if prefs.ExitNodeID != exitNodeID {
|
||||||
if !useAutoExitNode || !isAllowedAutoExitNodeID(prefs.ExitNodeID) {
|
if !useAutoExitNode || !isAllowedAutoExitNodeID(b.polc, prefs.ExitNodeID) {
|
||||||
prefs.ExitNodeID = exitNodeID
|
prefs.ExitNodeID = exitNodeID
|
||||||
anyChange = true
|
anyChange = true
|
||||||
}
|
}
|
||||||
@ -1926,7 +1925,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange
|
|||||||
prefs.ExitNodeIP = netip.Addr{}
|
prefs.ExitNodeIP = netip.Addr{}
|
||||||
anyChange = true
|
anyChange = true
|
||||||
}
|
}
|
||||||
} else if exitNodeIPStr, _ := syspolicy.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" {
|
} else if exitNodeIPStr, _ := b.polc.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" {
|
||||||
if prefs.AutoExitNode != "" {
|
if prefs.AutoExitNode != "" {
|
||||||
prefs.AutoExitNode = "" // mutually exclusive with ExitNodeIP
|
prefs.AutoExitNode = "" // mutually exclusive with ExitNodeIP
|
||||||
anyChange = true
|
anyChange = true
|
||||||
@ -1946,7 +1945,7 @@ func (b *LocalBackend) applyExitNodeSysPolicyLocked(prefs *ipn.Prefs) (anyChange
|
|||||||
// registerSysPolicyWatch subscribes to syspolicy change notifications
|
// registerSysPolicyWatch subscribes to syspolicy change notifications
|
||||||
// and immediately applies the effective syspolicy settings to the current profile.
|
// and immediately applies the effective syspolicy settings to the current profile.
|
||||||
func (b *LocalBackend) registerSysPolicyWatch() (unregister func(), err error) {
|
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)
|
return nil, fmt.Errorf("syspolicy: LocalBacked failed to register policy change callback: %v", err)
|
||||||
}
|
}
|
||||||
if prefs, anyChange := b.reconcilePrefs(); anyChange {
|
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) {
|
if _, err := b.SuggestExitNode(); err != nil && !errors.Is(err, ErrNoPreferredDERP) {
|
||||||
b.logf("failed to select auto exit node: %v", err)
|
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.
|
// 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 !b.lastSuggestedExitNode.IsZero() {
|
||||||
// If we have a suggested exit node, use it.
|
// If we have a suggested exit node, use it.
|
||||||
newExitNodeID = b.lastSuggestedExitNode
|
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
|
// If we don't have a suggested exit node, but the prefs already
|
||||||
// specify an allowed auto exit node ID, retain it.
|
// specify an allowed auto exit node ID, retain it.
|
||||||
newExitNodeID = prefs.ExitNodeID
|
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 == "" {
|
if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" {
|
||||||
sysak, _ := syspolicy.GetString(pkey.AuthKey, "")
|
sysak, _ := b.polc.GetString(pkey.AuthKey, "")
|
||||||
if sysak != "" {
|
if sysak != "" {
|
||||||
b.logf("Start: setting opts.AuthKey by syspolicy, len=%v", len(sysak))
|
b.logf("Start: setting opts.AuthKey by syspolicy, len=%v", len(sysak))
|
||||||
opts.AuthKey = strings.TrimSpace(sysak)
|
opts.AuthKey = strings.TrimSpace(sysak)
|
||||||
@ -4111,7 +4110,7 @@ func (b *LocalBackend) resolveBestProfileLocked() (_ ipn.LoginProfileView, isBac
|
|||||||
if b.currentUser != nil {
|
if b.currentUser != nil {
|
||||||
profile := b.pm.CurrentProfile()
|
profile := b.pm.CurrentProfile()
|
||||||
// TODO(nickkhyl): check if the current profile is allowed on the device,
|
// 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.
|
// See tailscale/corp#26249.
|
||||||
if uid := b.currentUser.UserID(); profile.LocalUserID() != uid {
|
if uid := b.currentUser.UserID(); profile.LocalUserID() != uid {
|
||||||
profile = b.pm.DefaultUserProfile(uid)
|
profile = b.pm.DefaultUserProfile(uid)
|
||||||
@ -4138,7 +4137,7 @@ func (b *LocalBackend) resolveBestProfileLocked() (_ ipn.LoginProfileView, isBac
|
|||||||
// using the current profile.
|
// using the current profile.
|
||||||
//
|
//
|
||||||
// TODO(nickkhyl): check if the current profile is allowed on the device,
|
// 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.
|
// See tailscale/corp#26249.
|
||||||
return b.pm.CurrentProfile(), false
|
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
|
// Prevent users from changing exit node preferences
|
||||||
// when exit node usage is managed by policy.
|
// when exit node usage is managed by policy.
|
||||||
if mp.ExitNodeIDSet || mp.ExitNodeIPSet || mp.AutoExitNodeSet {
|
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 {
|
if err != nil {
|
||||||
err = fmt.Errorf("policy check failed: %w", err)
|
err = fmt.Errorf("policy check failed: %w", err)
|
||||||
} else if isManaged {
|
} else if isManaged {
|
||||||
// Allow users to override ExitNode policy settings and select an exit node manually
|
// 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.
|
// 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) {
|
if !allowExitNodeOverride || b.changeDisablesExitNodeLocked(prefs, mp) {
|
||||||
err = errManagedByPolicy
|
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.
|
// 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) {
|
func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, oldPrefs, newPrefs ipn.PrefsView) {
|
||||||
if mp.WantRunningSet && !mp.WantRunning && oldPrefs.WantRunning() {
|
if mp.WantRunningSet && !mp.WantRunning && oldPrefs.WantRunning() {
|
||||||
// If a user has enough rights to disconnect, such as when [syspolicy.AlwaysOn]
|
// If a user has enough rights to disconnect, such as when [pkey.AlwaysOn]
|
||||||
// is disabled, or [syspolicy.AlwaysOnOverrideWithReason] is also set and the user
|
// is disabled, or [pkey.AlwaysOnOverrideWithReason] is also set and the user
|
||||||
// provides a reason for disconnecting, then we should not force the "always on"
|
// 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.
|
// mode on them until the policy changes, they switch to a different profile, etc.
|
||||||
b.overrideAlwaysOn = true
|
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)
|
b.startReconnectTimerLocked(reconnectAfter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4534,7 +4533,7 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o
|
|||||||
b.overrideExitNodePolicy = false
|
b.overrideExitNodePolicy = false
|
||||||
}
|
}
|
||||||
if mp.AutoExitNodeSet || mp.ExitNodeIDSet || mp.ExitNodeIPSet {
|
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,
|
// 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.
|
// the user is not overriding the policy. Otherwise, it is an override.
|
||||||
b.overrideExitNodePolicy = b.applyExitNodeSysPolicyLocked(newPrefs.AsStruct())
|
b.overrideExitNodePolicy = b.applyExitNodeSysPolicyLocked(newPrefs.AsStruct())
|
||||||
@ -5643,7 +5642,7 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip
|
|||||||
// was selected, if any.
|
// was selected, if any.
|
||||||
//
|
//
|
||||||
// If auto exit node is enabled (via [ipn.Prefs.AutoExitNode] or
|
// 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
|
// 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
|
// it (usually due to missing netmap or net report), then ExitNodeID in
|
||||||
// the prefs may be invalid (typically, [unresolvedExitNodeID]) until
|
// 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
|
// 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] {
|
func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
||||||
b.allowedSuggestedExitNodesMu.Lock()
|
b.allowedSuggestedExitNodesMu.Lock()
|
||||||
defer b.allowedSuggestedExitNodesMu.Unlock()
|
defer b.allowedSuggestedExitNodesMu.Unlock()
|
||||||
@ -7794,11 +7793,11 @@ func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// refreshAllowedSuggestions rebuilds the set of permitted exit nodes
|
// refreshAllowedSuggestions rebuilds the set of permitted exit nodes
|
||||||
// from the current [syspolicy.AllowedSuggestedExitNodes] value.
|
// from the current [pkey.AllowedSuggestedExitNodes] value.
|
||||||
func (b *LocalBackend) refreshAllowedSuggestions() {
|
func (b *LocalBackend) refreshAllowedSuggestions() {
|
||||||
b.allowedSuggestedExitNodesMu.Lock()
|
b.allowedSuggestedExitNodesMu.Lock()
|
||||||
defer b.allowedSuggestedExitNodesMu.Unlock()
|
defer b.allowedSuggestedExitNodesMu.Unlock()
|
||||||
b.allowedSuggestedExitNodes = fillAllowedSuggestions()
|
b.allowedSuggestedExitNodes = fillAllowedSuggestions(b.polc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectRegionFunc returns a DERP region from the slice of candidate regions.
|
// selectRegionFunc returns a DERP region from the slice of candidate regions.
|
||||||
@ -7810,8 +7809,8 @@ type selectRegionFunc func(views.Slice[int]) int
|
|||||||
// choice.
|
// choice.
|
||||||
type selectNodeFunc func(nodes views.Slice[tailcfg.NodeView], last tailcfg.StableNodeID) tailcfg.NodeView
|
type selectNodeFunc func(nodes views.Slice[tailcfg.NodeView], last tailcfg.StableNodeID) tailcfg.NodeView
|
||||||
|
|
||||||
func fillAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
func fillAllowedSuggestions(polc policyclient.Client) set.Set[tailcfg.StableNodeID] {
|
||||||
nodes, err := syspolicy.GetStringArray(pkey.AllowedSuggestedExitNodes, nil)
|
nodes, err := polc.GetStringArray(pkey.AllowedSuggestedExitNodes, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", pkey.AllowedSuggestedExitNodes, err)
|
log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", pkey.AllowedSuggestedExitNodes, err)
|
||||||
return nil
|
return nil
|
||||||
@ -8176,11 +8175,11 @@ const (
|
|||||||
unresolvedExitNodeID tailcfg.StableNodeID = "auto:any"
|
unresolvedExitNodeID tailcfg.StableNodeID = "auto:any"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isAllowedAutoExitNodeID(exitNodeID tailcfg.StableNodeID) bool {
|
func isAllowedAutoExitNodeID(polc policyclient.Client, exitNodeID tailcfg.StableNodeID) bool {
|
||||||
if exitNodeID == "" {
|
if exitNodeID == "" {
|
||||||
return false // an exit node is required
|
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))
|
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
|
// the Keychain. A future release will clean up the on-disk state
|
||||||
// files.
|
// files.
|
||||||
// TODO(#15830): always return true here once MacSys is fully migrated.
|
// 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)
|
return opt.NewBool(sp)
|
||||||
default:
|
default:
|
||||||
// Probably self-compiled tailscaled, we don't use the Keychain
|
// Probably self-compiled tailscaled, we don't use the Keychain
|
||||||
|
@ -47,6 +47,7 @@ import (
|
|||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsd"
|
"tailscale.com/tsd"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
|
"tailscale.com/tstest/deptest"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
"tailscale.com/types/ipproto"
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@ -63,6 +64,7 @@ import (
|
|||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
"tailscale.com/util/syspolicy"
|
"tailscale.com/util/syspolicy"
|
||||||
"tailscale.com/util/syspolicy/pkey"
|
"tailscale.com/util/syspolicy/pkey"
|
||||||
|
"tailscale.com/util/syspolicy/policyclient"
|
||||||
"tailscale.com/util/syspolicy/setting"
|
"tailscale.com/util/syspolicy/setting"
|
||||||
"tailscale.com/util/syspolicy/source"
|
"tailscale.com/util/syspolicy/source"
|
||||||
"tailscale.com/wgengine"
|
"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) {
|
func TestFillAllowedSuggestions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -5571,15 +5595,16 @@ func TestFillAllowedSuggestions(t *testing.T) {
|
|||||||
want: []tailcfg.StableNodeID{"ABC", "def", "gHiJ"},
|
want: []tailcfg.StableNodeID{"ABC", "def", "gHiJ"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
syspolicy.RegisterWellKnownSettingsForTest(t)
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
polc := testPolicy{
|
||||||
pkey.AllowedSuggestedExitNodes, tt.allowPolicy,
|
staticPolicy: staticPolicy{
|
||||||
))
|
pkey.AllowedSuggestedExitNodes: tt.allowPolicy,
|
||||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
},
|
||||||
|
}
|
||||||
|
|
||||||
got := fillAllowedSuggestions()
|
got := fillAllowedSuggestions(polc)
|
||||||
if got == nil {
|
if got == nil {
|
||||||
if tt.want == nil {
|
if tt.want == nil {
|
||||||
return
|
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) {
|
func checkError(tb testing.TB, got, want error, fatal bool) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
f := tb.Errorf
|
f := tb.Errorf
|
||||||
|
@ -6,9 +6,12 @@
|
|||||||
package tsd
|
package tsd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"tailscale.com/util/syspolicy"
|
"tailscale.com/util/syspolicy"
|
||||||
"tailscale.com/util/syspolicy/pkey"
|
"tailscale.com/util/syspolicy/pkey"
|
||||||
"tailscale.com/util/syspolicy/policyclient"
|
"tailscale.com/util/syspolicy/policyclient"
|
||||||
|
"tailscale.com/util/syspolicy/ptype"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPolicyClient() policyclient.Client { return globalSyspolicy{} }
|
func getPolicyClient() policyclient.Client { return globalSyspolicy{} }
|
||||||
@ -36,6 +39,26 @@ func (globalSyspolicy) SetDebugLoggingEnabled(enabled bool) {
|
|||||||
syspolicy.SetDebugLoggingEnabled(enabled)
|
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) {
|
func (globalSyspolicy) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) {
|
||||||
return syspolicy.RegisterChangeCallback(cb)
|
return syspolicy.RegisterChangeCallback(cb)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ import (
|
|||||||
type DepChecker struct {
|
type DepChecker struct {
|
||||||
GOOS string // optional
|
GOOS string // optional
|
||||||
GOARCH 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
|
BadDeps map[string]string // package => why
|
||||||
WantDeps set.Set[string] // packages expected
|
WantDeps set.Set[string] // packages expected
|
||||||
Tags string // comma-separated
|
Tags string // comma-separated
|
||||||
@ -52,6 +53,7 @@ func (c DepChecker) Check(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
var res struct {
|
var res struct {
|
||||||
|
Imports []string
|
||||||
Deps []string
|
Deps []string
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(out, &res); err != nil {
|
if err := json.Unmarshal(out, &res); err != nil {
|
||||||
@ -66,6 +68,12 @@ func (c DepChecker) Check(t *testing.T) {
|
|||||||
return strings.TrimSpace(string(out))
|
return strings.TrimSpace(string(out))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if c.OnImport != nil {
|
||||||
|
for _, imp := range res.Imports {
|
||||||
|
c.OnImport(imp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, dep := range res.Deps {
|
for _, dep := range res.Deps {
|
||||||
if c.OnDep != nil {
|
if c.OnDep != nil {
|
||||||
c.OnDep(dep)
|
c.OnDep(dep)
|
||||||
|
@ -6,7 +6,12 @@
|
|||||||
// of syspolicy is omitted from the build.
|
// of syspolicy is omitted from the build.
|
||||||
package policyclient
|
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
|
// Client is the interface between code making questions about the system policy
|
||||||
// and the actual implementation.
|
// and the actual implementation.
|
||||||
@ -23,9 +28,38 @@ type Client interface {
|
|||||||
// or defaultValue (and a nil error) if it does not exist.
|
// or defaultValue (and a nil error) if it does not exist.
|
||||||
GetBoolean(key pkey.Key, defaultValue bool) (bool, error)
|
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 enables or disables debug logging for the policy client.
|
||||||
SetDebugLoggingEnabled(enabled bool)
|
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
|
// RegisterChangeCallback registers a callback function that will be called
|
||||||
// whenever a policy change is detected. It returns a function to unregister
|
// whenever a policy change is detected. It returns a function to unregister
|
||||||
// the callback and an error if the registration fails.
|
// 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
|
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) SetDebugLoggingEnabled(enabled bool) {}
|
||||||
|
|
||||||
func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {
|
func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user