util/syspolicy: finish plumbing policyclient, add feature/syspolicy, move global impl

This is step 4 of making syspolicy a build-time feature.

This adds a policyclient.Get() accessor to return the correct
implementation to use: either the real one, or the no-op one. (A third
type, a static one for testing, also exists, so in general a
policyclient.Client should be plumbed around and not always fetched
via policyclient.Get whenever possible, especially if tests need to use
alternate syspolicy)

Updates #16998
Updates #12614

Change-Id: Iaf19670744a596d5918acfa744f5db4564272978
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2025-09-02 12:49:37 -07:00 committed by Brad Fitzpatrick
parent 9e9bf13063
commit 2b3e533048
44 changed files with 242 additions and 207 deletions

View File

@ -192,7 +192,7 @@ func (s *Server) controlSupportsCheckMode(ctx context.Context) bool {
if err != nil { if err != nil {
return true return true
} }
controlURL, err := url.Parse(prefs.ControlURLOrDefault()) controlURL, err := url.Parse(prefs.ControlURLOrDefault(s.polc))
if err != nil { if err != nil {
return true return true
} }

View File

@ -5,6 +5,7 @@
package web package web
import ( import (
"cmp"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -36,6 +37,7 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/httpm" "tailscale.com/util/httpm"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
@ -49,6 +51,7 @@ type Server struct {
mode ServerMode mode ServerMode
logf logger.Logf logf logger.Logf
polc policyclient.Client // must be non-nil
lc *local.Client lc *local.Client
timeNow func() time.Time timeNow func() time.Time
@ -139,9 +142,13 @@ type ServerOpts struct {
TimeNow func() time.Time TimeNow func() time.Time
// Logf optionally provides a logger function. // Logf optionally provides a logger function.
// log.Printf is used as default. // If nil, log.Printf is used as default.
Logf logger.Logf Logf logger.Logf
// PolicyClient, if non-nil, will be used to fetch policy settings.
// If nil, the default policy client will be used.
PolicyClient policyclient.Client
// The following two fields are required and used exclusively // The following two fields are required and used exclusively
// in ManageServerMode to facilitate the control server login // in ManageServerMode to facilitate the control server login
// check step for authorizing browser sessions. // check step for authorizing browser sessions.
@ -178,6 +185,7 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
} }
s = &Server{ s = &Server{
mode: opts.Mode, mode: opts.Mode,
polc: cmp.Or(opts.PolicyClient, policyclient.Get()),
logf: opts.Logf, logf: opts.Logf,
devMode: envknob.Bool("TS_DEBUG_WEB_CLIENT_DEV"), devMode: envknob.Bool("TS_DEBUG_WEB_CLIENT_DEV"),
lc: opts.LocalClient, lc: opts.LocalClient,
@ -950,7 +958,7 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"), UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
RunningSSHServer: prefs.RunSSH, RunningSSHServer: prefs.RunSSH,
URLPrefix: strings.TrimSuffix(s.pathPrefix, "/"), URLPrefix: strings.TrimSuffix(s.pathPrefix, "/"),
ControlAdminURL: prefs.AdminPageURL(), ControlAdminURL: prefs.AdminPageURL(s.polc),
LicensesURL: licenses.LicensesURL(), LicensesURL: licenses.LicensesURL(),
Features: availableFeatures(), Features: availableFeatures(),

View File

@ -28,6 +28,7 @@ import (
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/httpm" "tailscale.com/util/httpm"
"tailscale.com/util/syspolicy/policyclient"
) )
func TestQnapAuthnURL(t *testing.T) { func TestQnapAuthnURL(t *testing.T) {
@ -576,6 +577,7 @@ func TestServeAuth(t *testing.T) {
timeNow: func() time.Time { return timeNow }, timeNow: func() time.Time { return timeNow },
newAuthURL: mockNewAuthURL, newAuthURL: mockNewAuthURL,
waitAuthURL: mockWaitAuthURL, waitAuthURL: mockWaitAuthURL,
polc: policyclient.NoPolicyClient{},
} }
successCookie := "ts-cookie-success" successCookie := "ts-cookie-success"

View File

@ -170,21 +170,15 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/util/set from tailscale.com/derp+ tailscale.com/util/set from tailscale.com/derp+
tailscale.com/util/singleflight from tailscale.com/net/dnscache tailscale.com/util/singleflight from tailscale.com/net/dnscache
tailscale.com/util/slicesx from tailscale.com/cmd/derper+ tailscale.com/util/slicesx from tailscale.com/cmd/derper+
tailscale.com/util/syspolicy from tailscale.com/ipn tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting
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/pkey from tailscale.com/ipn+
tailscale.com/util/syspolicy/policyclient from tailscale.com/util/syspolicy/rsop tailscale.com/util/syspolicy/policyclient from tailscale.com/ipn
tailscale.com/util/syspolicy/ptype from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/ptype from tailscale.com/util/syspolicy/policyclient+
tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy tailscale.com/util/syspolicy/setting from tailscale.com/client/local
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/testenv from tailscale.com/net/bakedroots+
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
tailscale.com/util/testenv from tailscale.com/util/syspolicy+
tailscale.com/util/usermetric from tailscale.com/health tailscale.com/util/usermetric from tailscale.com/health
tailscale.com/util/vizerror from tailscale.com/tailcfg+ tailscale.com/util/vizerror from tailscale.com/tailcfg+
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+ W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
W 💣 tailscale.com/util/winutil/gp from tailscale.com/util/syspolicy/source
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+ W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
tailscale.com/version from tailscale.com/derp+ tailscale.com/version from tailscale.com/derp+
tailscale.com/version/distro from tailscale.com/envknob+ tailscale.com/version/distro from tailscale.com/envknob+
@ -205,7 +199,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from tailscale.com/util/winutil+ golang.org/x/exp/constraints from tailscale.com/util/winutil+
golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting+ golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting
L golang.org/x/net/bpf from github.com/mdlayher/netlink+ L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+ golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+ golang.org/x/net/http/httpguts from net/http+
@ -393,7 +387,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
os from crypto/internal/sysrand+ os from crypto/internal/sysrand+
os/exec from github.com/coreos/go-iptables/iptables+ os/exec from github.com/coreos/go-iptables/iptables+
os/signal from tailscale.com/cmd/derper os/signal from tailscale.com/cmd/derper
W os/user from tailscale.com/util/winutil+ W os/user from tailscale.com/util/winutil
path from github.com/prometheus/client_golang/prometheus/internal+ path from github.com/prometheus/client_golang/prometheus/internal+
path/filepath from crypto/x509+ path/filepath from crypto/x509+
reflect from crypto/x509+ reflect from crypto/x509+

View File

@ -798,6 +798,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/envknob from tailscale.com/client/local+ tailscale.com/envknob from tailscale.com/client/local+
tailscale.com/envknob/featureknob from tailscale.com/client/web+ tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+ tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/syspolicy from tailscale.com/logpolicy
tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health from tailscale.com/control/controlclient+
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+
tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/hostinfo from tailscale.com/client/web+
@ -951,7 +952,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/util/set from tailscale.com/cmd/k8s-operator+ tailscale.com/util/set from tailscale.com/cmd/k8s-operator+
tailscale.com/util/singleflight from tailscale.com/control/controlclient+ tailscale.com/util/singleflight from tailscale.com/control/controlclient+
tailscale.com/util/slicesx from tailscale.com/appc+ tailscale.com/util/slicesx from tailscale.com/appc+
tailscale.com/util/syspolicy from tailscale.com/ipn+ tailscale.com/util/syspolicy from tailscale.com/feature/syspolicy
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ 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/loggerx from tailscale.com/util/syspolicy/internal/metrics+
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source

View File

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_syspolicy
package cli
import _ "tailscale.com/feature/syspolicy"

View File

@ -39,6 +39,7 @@ import (
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
@ -609,7 +610,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
if env.upArgs.json { if env.upArgs.json {
printUpDoneJSON(ipn.NeedsMachineAuth, "") printUpDoneJSON(ipn.NeedsMachineAuth, "")
} else { } else {
fmt.Fprintf(Stderr, "\nTo approve your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL()) fmt.Fprintf(Stderr, "\nTo approve your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL(policyclient.Get()))
} }
case ipn.Running: case ipn.Running:
// Done full authentication process // Done full authentication process

View File

@ -106,6 +106,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/envknob/featureknob from tailscale.com/client/web tailscale.com/envknob/featureknob from tailscale.com/client/web
tailscale.com/feature from tailscale.com/tsweb tailscale.com/feature from tailscale.com/tsweb
tailscale.com/feature/capture/dissector from tailscale.com/cmd/tailscale/cli tailscale.com/feature/capture/dissector from tailscale.com/cmd/tailscale/cli
tailscale.com/feature/syspolicy from tailscale.com/cmd/tailscale/cli
tailscale.com/health from tailscale.com/net/tlsdial+ tailscale.com/health from tailscale.com/net/tlsdial+
tailscale.com/health/healthmsg from tailscale.com/cmd/tailscale/cli tailscale.com/health/healthmsg from tailscale.com/cmd/tailscale/cli
tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/hostinfo from tailscale.com/client/web+
@ -191,15 +192,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/util/singleflight from tailscale.com/net/dnscache+ tailscale.com/util/singleflight from tailscale.com/net/dnscache+
tailscale.com/util/slicesx from tailscale.com/net/dns/recursive+ tailscale.com/util/slicesx from tailscale.com/net/dns/recursive+
L tailscale.com/util/stringsx from tailscale.com/client/systray L tailscale.com/util/stringsx from tailscale.com/client/systray
tailscale.com/util/syspolicy from tailscale.com/ipn tailscale.com/util/syspolicy from tailscale.com/feature/syspolicy
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ 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/loggerx from tailscale.com/util/syspolicy+
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source 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/pkey from tailscale.com/ipn+
tailscale.com/util/syspolicy/policyclient from tailscale.com/util/syspolicy/rsop tailscale.com/util/syspolicy/policyclient from tailscale.com/client/web+
tailscale.com/util/syspolicy/ptype from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/ptype from tailscale.com/util/syspolicy/policyclient+
tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/setting from tailscale.com/client/local+
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+ tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli+ tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli+
tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli
@ -228,7 +229,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12 golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+ golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
golang.org/x/exp/maps from tailscale.com/util/syspolicy/internal/metrics+ golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting+
L golang.org/x/image/draw from github.com/fogleman/gg L golang.org/x/image/draw from github.com/fogleman/gg
L golang.org/x/image/font from github.com/fogleman/gg+ L golang.org/x/image/font from github.com/fogleman/gg+
L golang.org/x/image/font/basicfont from github.com/fogleman/gg L golang.org/x/image/font/basicfont from github.com/fogleman/gg

View File

@ -276,6 +276,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/feature/capture from tailscale.com/feature/condregister tailscale.com/feature/capture from tailscale.com/feature/condregister
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
tailscale.com/feature/relayserver from tailscale.com/feature/condregister tailscale.com/feature/relayserver from tailscale.com/feature/condregister
tailscale.com/feature/syspolicy from tailscale.com/feature/condregister+
tailscale.com/feature/taildrop from tailscale.com/feature/condregister tailscale.com/feature/taildrop from tailscale.com/feature/condregister
L tailscale.com/feature/tap from tailscale.com/feature/condregister L tailscale.com/feature/tap from tailscale.com/feature/condregister
tailscale.com/feature/tpm from tailscale.com/feature/condregister tailscale.com/feature/tpm from tailscale.com/feature/condregister
@ -428,7 +429,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/set from tailscale.com/derp+ tailscale.com/util/set from tailscale.com/derp+
tailscale.com/util/singleflight from tailscale.com/control/controlclient+ tailscale.com/util/singleflight from tailscale.com/control/controlclient+
tailscale.com/util/slicesx from tailscale.com/net/dns/recursive+ tailscale.com/util/slicesx from tailscale.com/net/dns/recursive+
tailscale.com/util/syspolicy from tailscale.com/cmd/tailscaled+ tailscale.com/util/syspolicy from tailscale.com/feature/syspolicy
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+ 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/loggerx from tailscale.com/util/syspolicy/internal/metrics+
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source

View File

@ -64,8 +64,8 @@ import (
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/multierr" "tailscale.com/util/multierr"
"tailscale.com/util/osshare" "tailscale.com/util/osshare"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
"tailscale.com/wgengine" "tailscale.com/wgengine"
@ -773,7 +773,7 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
// configuration being unavailable (from the noop // configuration being unavailable (from the noop
// manager). More in Issue 4017. // manager). More in Issue 4017.
// TODO(bradfitz): add a Synology-specific DNS manager. // TODO(bradfitz): add a Synology-specific DNS manager.
conf.DNS, err = dns.NewOSConfigurator(logf, sys.HealthTracker(), sys.ControlKnobs(), "") // empty interface name conf.DNS, err = dns.NewOSConfigurator(logf, sys.HealthTracker(), sys.PolicyClientOrDefault(), sys.ControlKnobs(), "") // empty interface name
if err != nil { if err != nil {
return false, fmt.Errorf("dns.NewOSConfigurator: %w", err) return false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
} }
@ -807,7 +807,7 @@ func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack boo
return false, fmt.Errorf("creating router: %w", err) return false, fmt.Errorf("creating router: %w", err)
} }
d, err := dns.NewOSConfigurator(logf, sys.HealthTracker(), sys.ControlKnobs(), devName) d, err := dns.NewOSConfigurator(logf, sys.HealthTracker(), sys.PolicyClientOrDefault(), sys.ControlKnobs(), devName)
if err != nil { if err != nil {
dev.Close() dev.Close()
r.Close() r.Close()
@ -1012,6 +1012,6 @@ func defaultEncryptState() bool {
// (plan9/FreeBSD/etc). // (plan9/FreeBSD/etc).
return false return false
} }
v, _ := syspolicy.GetBoolean(pkey.EncryptState, false) v, _ := policyclient.Get().GetBoolean(pkey.EncryptState, false)
return v return v
} }

View File

@ -55,8 +55,8 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid" "tailscale.com/types/logid"
"tailscale.com/util/osdiag" "tailscale.com/util/osdiag"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
"tailscale.com/util/winutil/gp" "tailscale.com/util/winutil/gp"
"tailscale.com/version" "tailscale.com/version"
@ -156,7 +156,7 @@ func runWindowsService(pol *logpolicy.Policy) error {
if syslog, err := eventlog.Open(serviceName); err == nil { if syslog, err := eventlog.Open(serviceName); err == nil {
syslogf = func(format string, args ...any) { syslogf = func(format string, args ...any) {
if logSCMInteractions, _ := syspolicy.GetBoolean(pkey.LogSCMInteractions, false); logSCMInteractions { if logSCMInteractions, _ := policyclient.Get().GetBoolean(pkey.LogSCMInteractions, false); logSCMInteractions {
syslog.Info(0, fmt.Sprintf(format, args...)) syslog.Info(0, fmt.Sprintf(format, args...))
} }
} }
@ -390,7 +390,7 @@ func handleSessionChange(chgRequest svc.ChangeRequest) {
if chgRequest.Cmd != svc.SessionChange || chgRequest.EventType != windows.WTS_SESSION_UNLOCK { if chgRequest.Cmd != svc.SessionChange || chgRequest.EventType != windows.WTS_SESSION_UNLOCK {
return return
} }
if flushDNSOnSessionUnlock, _ := syspolicy.GetBoolean(pkey.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock { if flushDNSOnSessionUnlock, _ := policyclient.Get().GetBoolean(pkey.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock {
log.Printf("Received WTS_SESSION_UNLOCK event, initiating DNS flush.") log.Printf("Received WTS_SESSION_UNLOCK event, initiating DNS flush.")
go func() { go func() {
err := dns.Flush() err := dns.Flush()

View File

@ -240,6 +240,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/envknob from tailscale.com/client/local+ tailscale.com/envknob from tailscale.com/client/local+
tailscale.com/envknob/featureknob from tailscale.com/client/web+ tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+ tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/syspolicy from tailscale.com/logpolicy
tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health from tailscale.com/control/controlclient+
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+
tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/hostinfo from tailscale.com/client/web+
@ -380,7 +381,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/util/set from tailscale.com/control/controlclient+ tailscale.com/util/set from tailscale.com/control/controlclient+
tailscale.com/util/singleflight from tailscale.com/control/controlclient+ tailscale.com/util/singleflight from tailscale.com/control/controlclient+
tailscale.com/util/slicesx from tailscale.com/appc+ tailscale.com/util/slicesx from tailscale.com/appc+
tailscale.com/util/syspolicy from tailscale.com/ipn+ tailscale.com/util/syspolicy from tailscale.com/feature/syspolicy
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy+ 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/loggerx from tailscale.com/util/syspolicy+
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source

View File

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_syspolicy
package condregister
import _ "tailscale.com/feature/syspolicy"

View File

@ -0,0 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package syspolicy provides an interface for system-wide policy management.
package syspolicy
import _ "tailscale.com/util/syspolicy" // for its registration side effects

View File

@ -18,8 +18,8 @@ import (
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnext" "tailscale.com/ipn/ipnext"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
) )
// featureName is the name of the feature implemented by this package. // featureName is the name of the feature implemented by this package.
@ -136,7 +136,7 @@ func (e *desktopSessionsExt) getBackgroundProfile(profiles ipnext.ProfileStore)
e.mu.Lock() e.mu.Lock()
defer e.mu.Unlock() defer e.mu.Unlock()
if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); !alwaysOn { if alwaysOn, _ := policyclient.Get().GetBoolean(pkey.AlwaysOn, false); !alwaysOn {
// If the Always-On mode is disabled, there's no background profile // If the Always-On mode is disabled, there's no background profile
// as far as the desktop session extension is concerned. // as far as the desktop session extension is concerned.
return ipn.LoginProfileView{} return ipn.LoginProfileView{}

View File

@ -10,8 +10,8 @@ import (
"tailscale.com/client/tailscale/apitype" "tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
) )
type actorWithPolicyChecks struct{ Actor } type actorWithPolicyChecks struct{ Actor }
@ -51,10 +51,10 @@ func (a actorWithPolicyChecks) CheckProfileAccess(profile ipn.LoginProfileView,
// TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver] // TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver]
// and corp to this package. // and corp to this package.
func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditFn AuditLogFunc) error { func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditFn AuditLogFunc) error {
if alwaysOn, _ := syspolicy.GetBoolean(pkey.AlwaysOn, false); !alwaysOn { if alwaysOn, _ := policyclient.Get().GetBoolean(pkey.AlwaysOn, false); !alwaysOn {
return nil return nil
} }
if allowWithReason, _ := syspolicy.GetBoolean(pkey.AlwaysOnOverrideWithReason, false); !allowWithReason { if allowWithReason, _ := policyclient.Get().GetBoolean(pkey.AlwaysOnOverrideWithReason, false); !allowWithReason {
return errors.New("disconnect not allowed: always-on mode is enabled") return errors.New("disconnect not allowed: always-on mode is enabled")
} }
if reason == "" { if reason == "" {

View File

@ -30,6 +30,7 @@ import (
"tailscale.com/util/goroutines" "tailscale.com/util/goroutines"
"tailscale.com/util/set" "tailscale.com/util/set"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/ptype"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "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 // 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 := b.polc.GetPreferenceOption(pkey.PostureChecking) choice, err := b.polc.GetPreferenceOption(pkey.PostureChecking, ptype.ShowChoiceByPolicy)
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",

View File

@ -109,6 +109,7 @@ import (
"tailscale.com/util/slicesx" "tailscale.com/util/slicesx"
"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"
"tailscale.com/util/systemd" "tailscale.com/util/systemd"
"tailscale.com/util/testenv" "tailscale.com/util/testenv"
"tailscale.com/util/usermetric" "tailscale.com/util/usermetric"
@ -1610,7 +1611,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
// future "tailscale up" to start checking for // future "tailscale up" to start checking for
// implicit setting reverts, which it doesn't do when // implicit setting reverts, which it doesn't do when
// ControlURL is blank. // ControlURL is blank.
prefs.ControlURL = prefs.ControlURLOrDefault() prefs.ControlURL = prefs.ControlURLOrDefault(b.polc)
prefsChanged = true prefsChanged = true
} }
if st.Persist.Valid() { if st.Persist.Valid() {
@ -1870,7 +1871,7 @@ func (b *LocalBackend) applySysPolicyLocked(prefs *ipn.Prefs) (anyChange bool) {
} }
for _, opt := range preferencePolicies { for _, opt := range preferencePolicies {
if po, err := b.polc.GetPreferenceOption(opt.key); err == nil { if po, err := b.polc.GetPreferenceOption(opt.key, ptype.ShowChoiceByPolicy); 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 {
@ -2425,7 +2426,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
loggedOut := prefs.LoggedOut() loggedOut := prefs.LoggedOut()
serverURL := prefs.ControlURLOrDefault() serverURL := prefs.ControlURLOrDefault(b.polc)
if inServerMode := prefs.ForceDaemon(); inServerMode || runtime.GOOS == "windows" { if inServerMode := prefs.ForceDaemon(); inServerMode || runtime.GOOS == "windows" {
b.logf("Start: serverMode=%v", inServerMode) b.logf("Start: serverMode=%v", inServerMode)
} }
@ -3498,7 +3499,7 @@ func (b *LocalBackend) validPopBrowserURLLocked(urlStr string) bool {
if err != nil { if err != nil {
return false return false
} }
serverURL := b.sanitizedPrefsLocked().ControlURLOrDefault() serverURL := b.sanitizedPrefsLocked().ControlURLOrDefault(b.polc)
if ipn.IsLoginServerSynonym(serverURL) { if ipn.IsLoginServerSynonym(serverURL) {
// When connected to the official Tailscale control plane, only allow // When connected to the official Tailscale control plane, only allow
// URLs from tailscale.com or its subdomains. // URLs from tailscale.com or its subdomains.
@ -4049,7 +4050,7 @@ func (b *LocalBackend) SwitchToBestProfile(reason string) {
// but b.mu must held on entry. It is released on exit. // but b.mu must held on entry. It is released on exit.
func (b *LocalBackend) switchToBestProfileLockedOnEntry(reason string, unlock unlockOnce) { func (b *LocalBackend) switchToBestProfileLockedOnEntry(reason string, unlock unlockOnce) {
defer unlock() defer unlock()
oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault() oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc)
profile, background := b.resolveBestProfileLocked() profile, background := b.resolveBestProfileLocked()
cp, switched, err := b.pm.SwitchToProfile(profile) cp, switched, err := b.pm.SwitchToProfile(profile)
switch { switch {
@ -4076,7 +4077,7 @@ func (b *LocalBackend) switchToBestProfileLockedOnEntry(reason string, unlock un
return return
} }
// As an optimization, only reset the dialPlan if the control URL changed. // As an optimization, only reset the dialPlan if the control URL changed.
if newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(); oldControlURL != newControlURL { if newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc); oldControlURL != newControlURL {
b.resetDialPlan() b.resetDialPlan()
} }
if err := b.resetForProfileChangeLockedOnEntry(unlock); err != nil { if err := b.resetForProfileChangeLockedOnEntry(unlock); err != nil {
@ -4250,7 +4251,7 @@ func (b *LocalBackend) isDefaultServerLocked() bool {
if !prefs.Valid() { if !prefs.Valid() {
return true // assume true until set otherwise return true // assume true until set otherwise
} }
return prefs.ControlURLOrDefault() == ipn.DefaultControlURL return prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL
} }
var exitNodeMisconfigurationWarnable = health.Register(&health.Warnable{ var exitNodeMisconfigurationWarnable = health.Register(&health.Warnable{
@ -5687,7 +5688,7 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State, unlock unlock
// Some temporary (2024-05-05) debugging code to help us catch // Some temporary (2024-05-05) debugging code to help us catch
// https://github.com/tailscale/tailscale/issues/11962 in the act. // https://github.com/tailscale/tailscale/issues/11962 in the act.
if prefs.WantRunning() && if prefs.WantRunning() &&
prefs.ControlURLOrDefault() == ipn.DefaultControlURL && prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL &&
envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") { envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
panic("[unexpected] use of main control server in integration test") panic("[unexpected] use of main control server in integration test")
} }
@ -7288,13 +7289,13 @@ func (b *LocalBackend) SwitchProfile(profile ipn.ProfileID) error {
unlock := b.lockAndGetUnlock() unlock := b.lockAndGetUnlock()
defer unlock() defer unlock()
oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault() oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc)
if _, changed, err := b.pm.SwitchToProfileByID(profile); !changed || err != nil { if _, changed, err := b.pm.SwitchToProfileByID(profile); !changed || err != nil {
return err // nil if we're already on the target profile return err // nil if we're already on the target profile
} }
// As an optimization, only reset the dialPlan if the control URL changed. // As an optimization, only reset the dialPlan if the control URL changed.
if newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(); oldControlURL != newControlURL { if newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc); oldControlURL != newControlURL {
b.resetDialPlan() b.resetDialPlan()
} }

View File

@ -28,8 +28,8 @@ import (
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/version" "tailscale.com/version"
) )
@ -718,16 +718,16 @@ func NewPrefs() *Prefs {
// //
// If not configured, or if the configured value is a legacy name equivalent to // If not configured, or if the configured value is a legacy name equivalent to
// the default, then DefaultControlURL is returned instead. // the default, then DefaultControlURL is returned instead.
func (p PrefsView) ControlURLOrDefault() string { func (p PrefsView) ControlURLOrDefault(polc policyclient.Client) string {
return p.ж.ControlURLOrDefault() return p.ж.ControlURLOrDefault(polc)
} }
// ControlURLOrDefault returns the coordination server's URL base. // ControlURLOrDefault returns the coordination server's URL base.
// //
// If not configured, or if the configured value is a legacy name equivalent to // If not configured, or if the configured value is a legacy name equivalent to
// the default, then DefaultControlURL is returned instead. // the default, then DefaultControlURL is returned instead.
func (p *Prefs) ControlURLOrDefault() string { func (p *Prefs) ControlURLOrDefault(polc policyclient.Client) string {
controlURL, err := syspolicy.GetString(pkey.ControlURL, p.ControlURL) controlURL, err := polc.GetString(pkey.ControlURL, p.ControlURL)
if err != nil { if err != nil {
controlURL = p.ControlURL controlURL = p.ControlURL
} }
@ -756,11 +756,11 @@ func (p *Prefs) DefaultRouteAll(goos string) bool {
} }
// AdminPageURL returns the admin web site URL for the current ControlURL. // AdminPageURL returns the admin web site URL for the current ControlURL.
func (p PrefsView) AdminPageURL() string { return p.ж.AdminPageURL() } func (p PrefsView) AdminPageURL(polc policyclient.Client) string { return p.ж.AdminPageURL(polc) }
// AdminPageURL returns the admin web site URL for the current ControlURL. // AdminPageURL returns the admin web site URL for the current ControlURL.
func (p *Prefs) AdminPageURL() string { func (p *Prefs) AdminPageURL(polc policyclient.Client) string {
url := p.ControlURLOrDefault() url := p.ControlURLOrDefault(polc)
if IsLoginServerSynonym(url) { if IsLoginServerSynonym(url) {
// TODO(crawshaw): In future release, make this https://console.tailscale.com // TODO(crawshaw): In future release, make this https://console.tailscale.com
url = "https://login.tailscale.com" url = "https://login.tailscale.com"

View File

@ -23,6 +23,7 @@ import (
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/types/persist" "tailscale.com/types/persist"
"tailscale.com/types/preftype" "tailscale.com/types/preftype"
"tailscale.com/util/syspolicy/policyclient"
) )
func fieldsOf(t reflect.Type) (fields []string) { func fieldsOf(t reflect.Type) (fields []string) {
@ -1032,15 +1033,16 @@ func TestExitNodeIPOfArg(t *testing.T) {
func TestControlURLOrDefault(t *testing.T) { func TestControlURLOrDefault(t *testing.T) {
var p Prefs var p Prefs
if got, want := p.ControlURLOrDefault(), DefaultControlURL; got != want { polc := policyclient.NoPolicyClient{}
if got, want := p.ControlURLOrDefault(polc), DefaultControlURL; got != want {
t.Errorf("got %q; want %q", got, want) t.Errorf("got %q; want %q", got, want)
} }
p.ControlURL = "http://foo.bar" p.ControlURL = "http://foo.bar"
if got, want := p.ControlURLOrDefault(), "http://foo.bar"; got != want { if got, want := p.ControlURLOrDefault(polc), "http://foo.bar"; got != want {
t.Errorf("got %q; want %q", got, want) t.Errorf("got %q; want %q", got, want)
} }
p.ControlURL = "https://login.tailscale.com" p.ControlURL = "https://login.tailscale.com"
if got, want := p.ControlURLOrDefault(), DefaultControlURL; got != want { if got, want := p.ControlURLOrDefault(polc), DefaultControlURL; got != want {
t.Errorf("got %q; want %q", got, want) t.Errorf("got %q; want %q", got, want)
} }
} }

View File

@ -51,8 +51,8 @@ import (
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/must" "tailscale.com/util/must"
"tailscale.com/util/racebuild" "tailscale.com/util/racebuild"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/testenv" "tailscale.com/util/testenv"
"tailscale.com/version" "tailscale.com/version"
"tailscale.com/version/distro" "tailscale.com/version/distro"
@ -66,7 +66,7 @@ var getLogTargetOnce struct {
func getLogTarget() string { func getLogTarget() string {
getLogTargetOnce.Do(func() { getLogTargetOnce.Do(func() {
envTarget, _ := os.LookupEnv("TS_LOG_TARGET") envTarget, _ := os.LookupEnv("TS_LOG_TARGET")
getLogTargetOnce.v, _ = syspolicy.GetString(pkey.LogTarget, envTarget) getLogTargetOnce.v, _ = policyclient.Get().GetString(pkey.LogTarget, envTarget)
}) })
return getLogTargetOnce.v return getLogTargetOnce.v

View File

@ -0,0 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_syspolicy
package logpolicy
import _ "tailscale.com/feature/syspolicy"

View File

@ -30,6 +30,7 @@ import (
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/slicesx" "tailscale.com/util/slicesx"
"tailscale.com/util/syspolicy/policyclient"
) )
var ( var (
@ -576,7 +577,7 @@ func (m *Manager) FlushCaches() error {
// //
// health must not be nil // health must not be nil
func CleanUp(logf logger.Logf, netMon *netmon.Monitor, health *health.Tracker, interfaceName string) { func CleanUp(logf logger.Logf, netMon *netmon.Monitor, health *health.Tracker, interfaceName string) {
oscfg, err := NewOSConfigurator(logf, nil, nil, interfaceName) oscfg, err := NewOSConfigurator(logf, health, policyclient.Get(), nil, interfaceName)
if err != nil { if err != nil {
logf("creating dns cleanup: %v", err) logf("creating dns cleanup: %v", err)
return return

View File

@ -14,12 +14,13 @@ import (
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/syspolicy/policyclient"
) )
// NewOSConfigurator creates a new OS configurator. // NewOSConfigurator creates a new OS configurator.
// //
// The health tracker and the knobs may be nil and are ignored on this platform. // The health tracker and the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logf logger.Logf, _ *health.Tracker, _ *controlknobs.Knobs, ifName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, _ *health.Tracker, _ policyclient.Client, _ *controlknobs.Knobs, ifName string) (OSConfigurator, error) {
return &darwinConfigurator{logf: logf, ifName: ifName}, nil return &darwinConfigurator{logf: logf, ifName: ifName}, nil
} }

View File

@ -9,11 +9,12 @@ import (
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/syspolicy/policyclient"
) )
// NewOSConfigurator creates a new OS configurator. // NewOSConfigurator creates a new OS configurator.
// //
// The health tracker and the knobs may be nil and are ignored on this platform. // The health tracker and the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logger.Logf, *health.Tracker, *controlknobs.Knobs, string) (OSConfigurator, error) { func NewOSConfigurator(logger.Logf, *health.Tracker, policyclient.Client, *controlknobs.Knobs, string) (OSConfigurator, error) {
return NewNoopManager() return NewNoopManager()
} }

View File

@ -10,12 +10,13 @@ import (
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/syspolicy/policyclient"
) )
// NewOSConfigurator creates a new OS configurator. // NewOSConfigurator creates a new OS configurator.
// //
// The health tracker may be nil; the knobs may be nil and are ignored on this platform. // The health tracker may be nil; the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, _ string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient.Client, _ *controlknobs.Knobs, _ string) (OSConfigurator, error) {
bs, err := os.ReadFile("/etc/resolv.conf") bs, err := os.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) { if os.IsNotExist(err) {
return newDirectManager(logf, health), nil return newDirectManager(logf, health), nil

View File

@ -22,6 +22,7 @@ import (
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/clientmetric" "tailscale.com/util/clientmetric"
"tailscale.com/util/cmpver" "tailscale.com/util/cmpver"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
@ -38,7 +39,7 @@ var publishOnce sync.Once
// NewOSConfigurator created a new OS configurator. // NewOSConfigurator created a new OS configurator.
// //
// The health tracker may be nil; the knobs may be nil and are ignored on this platform. // The health tracker may be nil; the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, interfaceName string) (ret OSConfigurator, err error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient.Client, _ *controlknobs.Knobs, interfaceName string) (ret OSConfigurator, err error) {
if distro.Get() == distro.JetKVM { if distro.Get() == distro.JetKVM {
return NewNoopManager() return NewNoopManager()
} }

View File

@ -11,6 +11,7 @@ import (
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/syspolicy/policyclient"
) )
type kv struct { type kv struct {
@ -24,7 +25,7 @@ func (kv kv) String() string {
// NewOSConfigurator created a new OS configurator. // NewOSConfigurator created a new OS configurator.
// //
// The health tracker may be nil; the knobs may be nil and are ignored on this platform. // The health tracker may be nil; the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient.Client, _ *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
return newOSConfigurator(logf, health, interfaceName, return newOSConfigurator(logf, health, interfaceName,
newOSConfigEnv{ newOSConfigEnv{
rcIsResolvd: rcIsResolvd, rcIsResolvd: rcIsResolvd,

View File

@ -21,9 +21,10 @@ import (
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/set" "tailscale.com/util/set"
"tailscale.com/util/syspolicy/policyclient"
) )
func NewOSConfigurator(logf logger.Logf, ht *health.Tracker, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, ht *health.Tracker, _ policyclient.Client, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
return &plan9DNSManager{ return &plan9DNSManager{
logf: logf, logf: logf,
ht: ht, ht: ht,

View File

@ -7,8 +7,9 @@ import (
"tailscale.com/control/controlknobs" "tailscale.com/control/controlknobs"
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/syspolicy/policyclient"
) )
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient.Client, _ *controlknobs.Knobs, iface string) (OSConfigurator, error) {
return newDirectManager(logf, health), nil return newDirectManager(logf, health), nil
} }

View File

@ -29,7 +29,6 @@ import (
"tailscale.com/health" "tailscale.com/health"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"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" "tailscale.com/util/syspolicy/ptype"
@ -48,6 +47,7 @@ type windowsManager struct {
knobs *controlknobs.Knobs // or nil knobs *controlknobs.Knobs // or nil
nrptDB *nrptRuleDatabase nrptDB *nrptRuleDatabase
wslManager *wslManager wslManager *wslManager
polc policyclient.Client
unregisterPolicyChangeCb func() // called when the manager is closing unregisterPolicyChangeCb func() // called when the manager is closing
@ -58,11 +58,15 @@ type windowsManager struct {
// NewOSConfigurator created a new OS configurator. // NewOSConfigurator created a new OS configurator.
// //
// The health tracker and the knobs may be nil. // The health tracker and the knobs may be nil.
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) { func NewOSConfigurator(logf logger.Logf, health *health.Tracker, polc policyclient.Client, knobs *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
if polc == nil {
panic("nil policyclient.Client")
}
ret := &windowsManager{ ret := &windowsManager{
logf: logf, logf: logf,
guid: interfaceName, guid: interfaceName,
knobs: knobs, knobs: knobs,
polc: polc,
wslManager: newWSLManager(logf, health), wslManager: newWSLManager(logf, health),
} }
@ -71,7 +75,7 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, knobs *controlk
} }
var err error var err error
if ret.unregisterPolicyChangeCb, err = syspolicy.RegisterChangeCallback(ret.sysPolicyChanged); err != nil { if ret.unregisterPolicyChangeCb, err = polc.RegisterChangeCallback(ret.sysPolicyChanged); err != nil {
logf("error registering policy change callback: %v", err) // non-fatal logf("error registering policy change callback: %v", err) // non-fatal
} }
@ -521,7 +525,7 @@ func (m *windowsManager) reconfigureDNSRegistration() {
// Disable DNS registration by default (if the policy setting is not configured). // Disable DNS registration by default (if the policy setting is not configured).
// This is primarily for historical reasons and to avoid breaking existing // This is primarily for historical reasons and to avoid breaking existing
// setups that rely on this behavior. // setups that rely on this behavior.
enableDNSRegistration, err := syspolicy.GetPreferenceOptionOrDefault(pkey.EnableDNSRegistration, ptype.NeverByPolicy) enableDNSRegistration, err := m.polc.GetPreferenceOption(pkey.EnableDNSRegistration, ptype.NeverByPolicy)
if err != nil { if err != nil {
m.logf("error getting DNSRegistration policy setting: %v", err) // non-fatal; we'll use the default m.logf("error getting DNSRegistration policy setting: %v", err) // non-fatal; we'll use the default
} }

View File

@ -17,6 +17,7 @@ import (
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/winutil" "tailscale.com/util/winutil"
"tailscale.com/util/winutil/gp" "tailscale.com/util/winutil/gp"
) )
@ -133,7 +134,7 @@ func TestManagerWindowsGPCopy(t *testing.T) {
} }
defer delIfKey() defer delIfKey()
cfg, err := NewOSConfigurator(logf, nil, nil, fakeInterface.String()) cfg, err := NewOSConfigurator(logf, nil, policyclient.NoPolicyClient{}, nil, fakeInterface.String())
if err != nil { if err != nil {
t.Fatalf("NewOSConfigurator: %v\n", err) t.Fatalf("NewOSConfigurator: %v\n", err)
} }
@ -262,7 +263,7 @@ func runTest(t *testing.T, isLocal bool) {
} }
defer delIfKey() defer delIfKey()
cfg, err := NewOSConfigurator(logf, nil, nil, fakeInterface.String()) cfg, err := NewOSConfigurator(logf, nil, policyclient.NoPolicyClient{}, nil, fakeInterface.String())
if err != nil { if err != nil {
t.Fatalf("NewOSConfigurator: %v\n", err) t.Fatalf("NewOSConfigurator: %v\n", err)
} }

View File

@ -1,12 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build ts_omit_syspolicy
package tsd
import (
"tailscale.com/util/syspolicy/policyclient"
)
func getPolicyClient() policyclient.Client { return policyclient.NoPolicyClient{} }

View File

@ -1,64 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !ts_omit_syspolicy
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{} }
// globalSyspolicy implements [policyclient.Client] using the syspolicy global
// functions and global registrations.
//
// TODO: de-global-ify. This implementation using the old global functions
// is an intermediate stage while changing policyclient to be modular.
type globalSyspolicy struct{}
func (globalSyspolicy) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
return syspolicy.GetBoolean(key, defaultValue)
}
func (globalSyspolicy) GetString(key pkey.Key, defaultValue string) (string, error) {
return syspolicy.GetString(key, defaultValue)
}
func (globalSyspolicy) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
return syspolicy.GetStringArray(key, defaultValue)
}
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)
}

View File

@ -175,7 +175,7 @@ func (s *System) PolicyClientOrDefault() policyclient.Client {
if client, ok := s.PolicyClient.GetOK(); ok { if client, ok := s.PolicyClient.GetOK(); ok {
return client return client
} }
return getPolicyClient() return policyclient.Get()
} }
// SubSystem represents some subsystem of the Tailscale node daemon. // SubSystem represents some subsystem of the Tailscale node daemon.

View File

@ -236,6 +236,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/envknob from tailscale.com/client/local+ tailscale.com/envknob from tailscale.com/client/local+
tailscale.com/envknob/featureknob from tailscale.com/client/web+ tailscale.com/envknob/featureknob from tailscale.com/client/web+
tailscale.com/feature from tailscale.com/ipn/ipnext+ tailscale.com/feature from tailscale.com/ipn/ipnext+
tailscale.com/feature/syspolicy from tailscale.com/logpolicy
tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/health from tailscale.com/control/controlclient+
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+ tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal+
tailscale.com/hostinfo from tailscale.com/client/web+ tailscale.com/hostinfo from tailscale.com/client/web+
@ -375,7 +376,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/util/set from tailscale.com/control/controlclient+ tailscale.com/util/set from tailscale.com/control/controlclient+
tailscale.com/util/singleflight from tailscale.com/control/controlclient+ tailscale.com/util/singleflight from tailscale.com/control/controlclient+
tailscale.com/util/slicesx from tailscale.com/appc+ tailscale.com/util/slicesx from tailscale.com/appc+
tailscale.com/util/syspolicy from tailscale.com/ipn+ tailscale.com/util/syspolicy from tailscale.com/feature/syspolicy
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy+ 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/loggerx from tailscale.com/util/syspolicy+
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source

View File

@ -51,8 +51,8 @@ import (
_ "tailscale.com/util/eventbus" _ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr" _ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare" _ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/syspolicy/pkey"
_ "tailscale.com/util/syspolicy/policyclient"
_ "tailscale.com/version" _ "tailscale.com/version"
_ "tailscale.com/version/distro" _ "tailscale.com/version/distro"
_ "tailscale.com/wgengine" _ "tailscale.com/wgengine"

View File

@ -51,8 +51,8 @@ import (
_ "tailscale.com/util/eventbus" _ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr" _ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare" _ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/syspolicy/pkey"
_ "tailscale.com/util/syspolicy/policyclient"
_ "tailscale.com/version" _ "tailscale.com/version"
_ "tailscale.com/version/distro" _ "tailscale.com/version/distro"
_ "tailscale.com/wgengine" _ "tailscale.com/wgengine"

View File

@ -51,8 +51,8 @@ import (
_ "tailscale.com/util/eventbus" _ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr" _ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare" _ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/syspolicy/pkey"
_ "tailscale.com/util/syspolicy/policyclient"
_ "tailscale.com/version" _ "tailscale.com/version"
_ "tailscale.com/version/distro" _ "tailscale.com/version/distro"
_ "tailscale.com/wgengine" _ "tailscale.com/wgengine"

View File

@ -51,8 +51,8 @@ import (
_ "tailscale.com/util/eventbus" _ "tailscale.com/util/eventbus"
_ "tailscale.com/util/multierr" _ "tailscale.com/util/multierr"
_ "tailscale.com/util/osshare" _ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/syspolicy/pkey"
_ "tailscale.com/util/syspolicy/policyclient"
_ "tailscale.com/version" _ "tailscale.com/version"
_ "tailscale.com/version/distro" _ "tailscale.com/version/distro"
_ "tailscale.com/wgengine" _ "tailscale.com/wgengine"

View File

@ -62,8 +62,8 @@ import (
_ "tailscale.com/util/multierr" _ "tailscale.com/util/multierr"
_ "tailscale.com/util/osdiag" _ "tailscale.com/util/osdiag"
_ "tailscale.com/util/osshare" _ "tailscale.com/util/osshare"
_ "tailscale.com/util/syspolicy"
_ "tailscale.com/util/syspolicy/pkey" _ "tailscale.com/util/syspolicy/pkey"
_ "tailscale.com/util/syspolicy/policyclient"
_ "tailscale.com/util/winutil" _ "tailscale.com/util/winutil"
_ "tailscale.com/util/winutil/gp" _ "tailscale.com/util/winutil/gp"
_ "tailscale.com/version" _ "tailscale.com/version"

View File

@ -44,8 +44,8 @@ type Client interface {
// overrides of users' choices in a way that we do not want tailcontrol to have // 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 // 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 // "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. // present or set to a different value, defaultValue (and a nil error) is returned.
GetPreferenceOption(key pkey.Key) (ptype.PreferenceOption, error) GetPreferenceOption(key pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error)
// GetVisibility returns whether a UI element should be visible based on // GetVisibility returns whether a UI element should be visible based on
// the system's configuration. // the system's configuration.
@ -66,6 +66,21 @@ type Client interface {
RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error)
} }
// Get returns a non-nil [Client] implementation as a function of the
// build tags. It returns a no-op implementation if the full syspolicy
// package is omitted from the build.
func Get() Client {
return client
}
// RegisterClientImpl registers a [Client] implementation to be returned by
// [Get].
func RegisterClientImpl(c Client) {
client = c
}
var client Client = NoPolicyClient{}
// PolicyChange is the interface representing a change in policy settings. // PolicyChange is the interface representing a change in policy settings.
type PolicyChange interface { type PolicyChange interface {
// HasChanged reports whether the policy setting identified by the given key // HasChanged reports whether the policy setting identified by the given key
@ -81,6 +96,8 @@ type PolicyChange interface {
// returns default values. // returns default values.
type NoPolicyClient struct{} type NoPolicyClient struct{}
var _ Client = NoPolicyClient{}
func (NoPolicyClient) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) { func (NoPolicyClient) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
return defaultValue, nil return defaultValue, nil
} }
@ -101,8 +118,8 @@ func (NoPolicyClient) GetDuration(name pkey.Key, defaultValue time.Duration) (ti
return defaultValue, nil return defaultValue, nil
} }
func (NoPolicyClient) GetPreferenceOption(name pkey.Key) (ptype.PreferenceOption, error) { func (NoPolicyClient) GetPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
return ptype.ShowChoiceByPolicy, nil return defaultValue, nil
} }
func (NoPolicyClient) GetVisibility(name pkey.Key) (ptype.Visibility, error) { func (NoPolicyClient) GetVisibility(name pkey.Key) (ptype.Visibility, error) {

View File

@ -1,13 +1,9 @@
// Copyright (c) Tailscale Inc & AUTHORS // Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// Package syspolicy facilitates retrieval of the current policy settings // Package syspolicy contains the implementation of system policy management.
// applied to the device or user and receiving notifications when the policy // Calling code should use the client interface in
// changes. // tailscale.com/util/syspolicy/policyclient.
//
// It provides functions that return specific policy settings by their unique
// [setting.Key]s, such as [GetBoolean], [GetUint64], [GetString],
// [GetStringArray], [GetPreferenceOption], [GetVisibility] and [GetDuration].
package syspolicy package syspolicy
import ( import (
@ -18,6 +14,7 @@ import (
"tailscale.com/util/syspolicy/internal/loggerx" "tailscale.com/util/syspolicy/internal/loggerx"
"tailscale.com/util/syspolicy/pkey" "tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
"tailscale.com/util/syspolicy/ptype" "tailscale.com/util/syspolicy/ptype"
"tailscale.com/util/syspolicy/rsop" "tailscale.com/util/syspolicy/rsop"
"tailscale.com/util/syspolicy/setting" "tailscale.com/util/syspolicy/setting"
@ -58,9 +55,9 @@ func MustRegisterStoreForTest(tb testenv.TB, name string, scope setting.PolicySc
return reg return reg
} }
// HasAnyOf returns whether at least one of the specified policy settings is configured, // 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. // or an error if no keys are provided or the check fails.
func HasAnyOf(keys ...pkey.Key) (bool, error) { func hasAnyOf(keys ...pkey.Key) (bool, error) {
if len(keys) == 0 { if len(keys) == 0 {
return false, errors.New("at least one key must be specified") return false, errors.New("at least one key must be specified")
} }
@ -82,62 +79,55 @@ func HasAnyOf(keys ...pkey.Key) (bool, error) {
return false, nil return false, nil
} }
// GetString returns a string policy setting with the specified key, // getString returns a string policy setting with the specified key,
// or defaultValue if it does not exist. // or defaultValue if it does not exist.
func GetString(key pkey.Key, defaultValue string) (string, error) { func getString(key pkey.Key, defaultValue string) (string, error) {
return getCurrentPolicySettingValue(key, defaultValue) return getCurrentPolicySettingValue(key, defaultValue)
} }
// GetUint64 returns a numeric policy setting with the specified key, // getUint64 returns a numeric policy setting with the specified key,
// or defaultValue if it does not exist. // or defaultValue if it does not exist.
func GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) { func getUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
return getCurrentPolicySettingValue(key, defaultValue) return getCurrentPolicySettingValue(key, defaultValue)
} }
// GetBoolean returns a boolean policy setting with the specified key, // getBoolean returns a boolean policy setting with the specified key,
// or defaultValue if it does not exist. // or defaultValue if it does not exist.
func GetBoolean(key pkey.Key, defaultValue bool) (bool, error) { func getBoolean(key pkey.Key, defaultValue bool) (bool, error) {
return getCurrentPolicySettingValue(key, defaultValue) return getCurrentPolicySettingValue(key, defaultValue)
} }
// GetStringArray returns a multi-string policy setting with the specified key, // getStringArray returns a multi-string policy setting with the specified key,
// or defaultValue if it does not exist. // or defaultValue if it does not exist.
func GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) { func getStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
return getCurrentPolicySettingValue(key, defaultValue) return getCurrentPolicySettingValue(key, defaultValue)
} }
// GetPreferenceOption loads a policy from the registry that can be // getPreferenceOption loads a policy from the registry that can be
// managed by an enterprise policy management system and allows administrative // 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 // 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 // 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 // "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. // present or set to a different value, defaultValue (and a nil error) is returned.
func GetPreferenceOption(name pkey.Key) (ptype.PreferenceOption, error) { func getPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
return getCurrentPolicySettingValue(name, ptype.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 pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
return getCurrentPolicySettingValue(name, defaultValue) return getCurrentPolicySettingValue(name, defaultValue)
} }
// GetVisibility loads a policy from the registry that can be managed // getVisibility loads a policy from the registry that can be managed
// by an enterprise policy management system and describes show/hide decisions // by an enterprise policy management system and describes show/hide decisions
// for UI elements. The registry value should be a string set to "show" (return // 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, // true) or "hide" (return true). If not present or set to a different value,
// "show" (return false) is the default. // "show" (return false) is the default.
func GetVisibility(name pkey.Key) (ptype.Visibility, error) { func getVisibility(name pkey.Key) (ptype.Visibility, error) {
return getCurrentPolicySettingValue(name, ptype.VisibleByPolicy) return getCurrentPolicySettingValue(name, ptype.VisibleByPolicy)
} }
// GetDuration loads a policy from the registry that can be managed // getDuration loads a policy from the registry that can be managed
// by an enterprise policy management system and describes a duration for some // by an enterprise policy management system and describes a duration for some
// action. The registry value should be a string that time.ParseDuration // action. The registry value should be a string that time.ParseDuration
// understands. If the registry value is "" or can not be processed, // understands. If the registry value is "" or can not be processed,
// defaultValue is returned instead. // defaultValue is returned instead.
func GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) { func getDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
d, err := getCurrentPolicySettingValue(name, defaultValue) d, err := getCurrentPolicySettingValue(name, defaultValue)
if err != nil { if err != nil {
return d, err return d, err
@ -148,9 +138,9 @@ func GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, erro
return d, nil return d, nil
} }
// RegisterChangeCallback adds a function that will be called whenever the effective policy // registerChangeCallback adds a function that will be called whenever the effective policy
// for the default scope changes. The returned function can be used to unregister the callback. // for the default scope changes. The returned function can be used to unregister the callback.
func RegisterChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), err error) { func registerChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), err error) {
effective, err := rsop.PolicyFor(setting.DefaultScope()) effective, err := rsop.PolicyFor(setting.DefaultScope())
if err != nil { if err != nil {
return nil, err return nil, err
@ -233,7 +223,53 @@ func SelectControlURL(reg, disk string) string {
return def return def
} }
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled. func init() {
func SetDebugLoggingEnabled(v bool) { policyclient.RegisterClientImpl(globalSyspolicy{})
loggerx.SetDebugLoggingEnabled(v) }
// globalSyspolicy implements [policyclient.Client] using the syspolicy global
// functions and global registrations.
//
// TODO: de-global-ify. This implementation using the old global functions
// is an intermediate stage while changing policyclient to be modular.
type globalSyspolicy struct{}
func (globalSyspolicy) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
return getBoolean(key, defaultValue)
}
func (globalSyspolicy) GetString(key pkey.Key, defaultValue string) (string, error) {
return getString(key, defaultValue)
}
func (globalSyspolicy) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
return getStringArray(key, defaultValue)
}
func (globalSyspolicy) SetDebugLoggingEnabled(enabled bool) {
loggerx.SetDebugLoggingEnabled(enabled)
}
func (globalSyspolicy) GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
return getUint64(key, defaultValue)
}
func (globalSyspolicy) GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
return getDuration(name, defaultValue)
}
func (globalSyspolicy) GetPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
return getPreferenceOption(name, defaultValue)
}
func (globalSyspolicy) GetVisibility(name pkey.Key) (ptype.Visibility, error) {
return getVisibility(name)
}
func (globalSyspolicy) HasAnyOf(keys ...pkey.Key) (bool, error) {
return hasAnyOf(keys...)
}
func (globalSyspolicy) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) {
return registerChangeCallback(cb)
} }

View File

@ -82,7 +82,7 @@ func TestGetString(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
value, err := GetString(tt.key, tt.defaultValue) value, err := getString(tt.key, tt.defaultValue)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -157,7 +157,7 @@ func TestGetUint64(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
value, err := GetUint64(tt.key, tt.defaultValue) value, err := getUint64(tt.key, tt.defaultValue)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -224,7 +224,7 @@ func TestGetBoolean(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
value, err := GetBoolean(tt.key, tt.defaultValue) value, err := getBoolean(tt.key, tt.defaultValue)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -317,7 +317,7 @@ func TestGetPreferenceOption(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
option, err := GetPreferenceOption(tt.key) option, err := getPreferenceOption(tt.key, ptype.ShowChoiceByPolicy)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -402,7 +402,7 @@ func TestGetVisibility(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
visibility, err := GetVisibility(tt.key) visibility, err := getVisibility(tt.key)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -498,7 +498,7 @@ func TestGetDuration(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
duration, err := GetDuration(tt.key, tt.defaultValue) duration, err := getDuration(tt.key, tt.defaultValue)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -579,7 +579,7 @@ func TestGetStringArray(t *testing.T) {
} }
registerSingleSettingStoreForTest(t, s) registerSingleSettingStoreForTest(t, s)
value, err := GetStringArray(tt.key, tt.defaultValue) value, err := getStringArray(tt.key, tt.defaultValue)
if !errorsMatchForTest(err, tt.wantError) { if !errorsMatchForTest(err, tt.wantError) {
t.Errorf("err=%q, want %q", err, tt.wantError) t.Errorf("err=%q, want %q", err, tt.wantError)
} }
@ -613,7 +613,7 @@ func BenchmarkGetString(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
gotControlURL, _ := GetString(pkey.ControlURL, "https://controlplane.tailscale.com") gotControlURL, _ := getString(pkey.ControlURL, "https://controlplane.tailscale.com")
if gotControlURL != wantControlURL { if gotControlURL != wantControlURL {
b.Fatalf("got %v; want %v", gotControlURL, wantControlURL) b.Fatalf("got %v; want %v", gotControlURL, wantControlURL)
} }