mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +00:00
util/syspolicy/policyclient: add Client interface to the syspolicy universe
This removes the dependency on syspolicy/... from LocalBackend and tailscaled when ts_omit_syspolicy is true. Updates #12614 Change-Id: I309deb0f50f8e7d6bc11454e4210bb3b358abc77 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
b50d32059f
commit
3116dbefac
@ -37,7 +37,7 @@ while [ "$#" -gt 1 ]; do
|
||||
--extra-small)
|
||||
shift
|
||||
ldflags="$ldflags -w -s"
|
||||
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion"
|
||||
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_syspolicy"
|
||||
;;
|
||||
--box)
|
||||
shift
|
||||
|
@ -40,7 +40,6 @@ import (
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/tkatype"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
// defaultLocalClient is the default LocalClient when using the legacy
|
||||
@ -832,33 +831,6 @@ func (lc *LocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn
|
||||
return decodeJSON[*ipn.Prefs](body)
|
||||
}
|
||||
|
||||
// GetEffectivePolicy returns the effective policy for the specified scope.
|
||||
func (lc *LocalClient) GetEffectivePolicy(ctx context.Context, scope setting.PolicyScope) (*setting.Snapshot, error) {
|
||||
scopeID, err := scope.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := lc.get200(ctx, "/localapi/v0/policy/"+string(scopeID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeJSON[*setting.Snapshot](body)
|
||||
}
|
||||
|
||||
// ReloadEffectivePolicy reloads the effective policy for the specified scope
|
||||
// by reading and merging policy settings from all applicable policy sources.
|
||||
func (lc *LocalClient) ReloadEffectivePolicy(ctx context.Context, scope setting.PolicyScope) (*setting.Snapshot, error) {
|
||||
scopeID, err := scope.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := lc.send(ctx, "POST", "/localapi/v0/policy/"+string(scopeID), 200, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeJSON[*setting.Snapshot](body)
|
||||
}
|
||||
|
||||
// GetDNSOSConfig returns the system DNS configuration for the current device.
|
||||
// That is, it returns the DNS configuration that the system would use if Tailscale weren't being used.
|
||||
func (lc *LocalClient) GetDNSOSConfig(ctx context.Context) (*apitype.DNSOSConfig, error) {
|
||||
|
40
client/tailscale/syspolicy.go
Normal file
40
client/tailscale/syspolicy.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
// GetEffectivePolicy returns the effective policy for the specified scope.
|
||||
func (lc *LocalClient) GetEffectivePolicy(ctx context.Context, scope setting.PolicyScope) (*setting.Snapshot, error) {
|
||||
scopeID, err := scope.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := lc.get200(ctx, "/localapi/v0/policy/"+string(scopeID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeJSON[*setting.Snapshot](body)
|
||||
}
|
||||
|
||||
// ReloadEffectivePolicy reloads the effective policy for the specified scope
|
||||
// by reading and merging policy settings from all applicable policy sources.
|
||||
func (lc *LocalClient) ReloadEffectivePolicy(ctx context.Context, scope setting.PolicyScope) (*setting.Snapshot, error) {
|
||||
scopeID, err := scope.MarshalText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := lc.send(ctx, "POST", "/localapi/v0/policy/"+string(scopeID), 200, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodeJSON[*setting.Snapshot](body)
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -192,7 +193,7 @@ func (s *Server) controlSupportsCheckMode(ctx context.Context) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
controlURL, err := url.Parse(prefs.ControlURLOrDefault())
|
||||
controlURL, err := url.Parse(prefs.ControlURLOrDefault(policyclient.NoPolicyClient{})) // XXX plumb
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/httpm"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@ -884,7 +885,7 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
|
||||
UnraidToken: os.Getenv("UNRAID_CSRF_TOKEN"),
|
||||
RunningSSHServer: prefs.RunSSH,
|
||||
URLPrefix: strings.TrimSuffix(s.pathPrefix, "/"),
|
||||
ControlAdminURL: prefs.AdminPageURL(),
|
||||
ControlAdminURL: prefs.AdminPageURL(policyclient.NoPolicyClient{}), // XXX TODO: plumb
|
||||
LicensesURL: licenses.LicensesURL(),
|
||||
Features: availableFeatures(),
|
||||
|
||||
|
@ -161,18 +161,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/set from tailscale.com/derp+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache
|
||||
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/loggerx from tailscale.com/util/syspolicy/internal/metrics+
|
||||
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source
|
||||
tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/testenv from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting
|
||||
tailscale.com/util/syspolicy/pkey from tailscale.com/ipn+
|
||||
tailscale.com/util/syspolicy/policyclient from tailscale.com/ipn
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/client/tailscale
|
||||
tailscale.com/util/usermetric from tailscale.com/health
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
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+
|
||||
tailscale.com/version from tailscale.com/derp+
|
||||
tailscale.com/version/distro from tailscale.com/envknob+
|
||||
@ -193,7 +188,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/sha3 from crypto/internal/mlkem768+
|
||||
W 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+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from net/http
|
||||
@ -253,7 +248,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
encoding/pem from crypto/tls+
|
||||
errors from bufio+
|
||||
expvar from github.com/prometheus/client_golang/prometheus+
|
||||
flag from tailscale.com/cmd/derper+
|
||||
flag from tailscale.com/cmd/derper
|
||||
fmt from compress/flate+
|
||||
go/token from google.golang.org/protobuf/internal/strs
|
||||
hash from crypto+
|
||||
@ -288,7 +283,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
os from crypto/rand+
|
||||
os/exec from github.com/coreos/go-iptables/iptables+
|
||||
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/filepath from crypto/x509+
|
||||
reflect from crypto/x509+
|
||||
|
@ -942,13 +942,14 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/util/set from tailscale.com/cmd/k8s-operator+
|
||||
tailscale.com/util/singleflight from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/slicesx from tailscale.com/appc+
|
||||
tailscale.com/util/syspolicy from tailscale.com/control/controlclient+
|
||||
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/rsop from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/pkey from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/syspolicy/policyclient from tailscale.com/client/web+
|
||||
tailscale.com/util/syspolicy/rsop from tailscale.com/ipn/localapi
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/client/tailscale+
|
||||
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy/rsop
|
||||
tailscale.com/util/sysresources from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/util/systemd from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/testenv from tailscale.com/control/controlclient+
|
||||
|
@ -21,23 +21,25 @@ var advertiseArgs struct {
|
||||
|
||||
// TODO(naman): This flag may move to set.go or serve_v2.go after the WIPCode
|
||||
// envknob is not needed.
|
||||
var advertiseCmd = &ffcli.Command{
|
||||
Name: "advertise",
|
||||
ShortUsage: "tailscale advertise --services=<services>",
|
||||
ShortHelp: "Advertise this node as a destination for a service",
|
||||
Exec: runAdvertise,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("advertise")
|
||||
fs.StringVar(&advertiseArgs.services, "services", "", "comma-separated services to advertise; each must start with \"svc:\" (e.g. \"svc:idp,svc:nas,svc:database\")")
|
||||
return fs
|
||||
})(),
|
||||
}
|
||||
|
||||
func maybeAdvertiseCmd() []*ffcli.Command {
|
||||
// advertiseCmd returns the "tailscale advertise" command
|
||||
// if WIP is enabled, else nil, which gets filtered out.
|
||||
func advertiseCmd() *ffcli.Command {
|
||||
if !envknob.UseWIPCode() {
|
||||
return nil
|
||||
}
|
||||
return []*ffcli.Command{advertiseCmd}
|
||||
|
||||
return &ffcli.Command{
|
||||
Name: "advertise",
|
||||
ShortUsage: "tailscale advertise --services=<services>",
|
||||
ShortHelp: "Advertise this node as a destination for a service",
|
||||
Exec: runAdvertise,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("advertise")
|
||||
fs.StringVar(&advertiseArgs.services, "services", "", "comma-separated services to advertise; each must start with \"svc:\" (e.g. \"svc:idp,svc:nas,svc:database\")")
|
||||
return fs
|
||||
})(),
|
||||
}
|
||||
}
|
||||
|
||||
func runAdvertise(ctx context.Context, args []string) error {
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"tailscale.com/cmd/tailscale/cli/ffcomplete"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/util/slicesx"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
@ -163,6 +164,11 @@ func Run(args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
// Set by syspolicy.go. See [mkSyspolicyCmd].
|
||||
syspolicyCmd = func() *ffcli.Command { return nil }
|
||||
)
|
||||
|
||||
func newRootCmd() *ffcli.Command {
|
||||
rootfs := newFlagSet("tailscale")
|
||||
rootfs.Func("socket", "path to tailscaled socket", func(s string) error {
|
||||
@ -182,7 +188,7 @@ For help on subcommands, add --help after: "tailscale status --help".
|
||||
This CLI is still under active development. Commands and flags will
|
||||
change in the future.
|
||||
`),
|
||||
Subcommands: append([]*ffcli.Command{
|
||||
Subcommands: nonNilCmds(
|
||||
upCmd,
|
||||
downCmd,
|
||||
setCmd,
|
||||
@ -190,7 +196,7 @@ change in the future.
|
||||
logoutCmd,
|
||||
switchCmd,
|
||||
configureCmd,
|
||||
syspolicyCmd,
|
||||
syspolicyCmd(),
|
||||
netcheckCmd,
|
||||
ipCmd,
|
||||
dnsCmd,
|
||||
@ -214,7 +220,8 @@ change in the future.
|
||||
debugCmd,
|
||||
driveCmd,
|
||||
idTokenCmd,
|
||||
}, maybeAdvertiseCmd()...),
|
||||
advertiseCmd(),
|
||||
),
|
||||
FlagSet: rootfs,
|
||||
Exec: func(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
@ -239,6 +246,10 @@ change in the future.
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func nonNilCmds(cmds ...*ffcli.Command) []*ffcli.Command {
|
||||
return slicesx.Filter(cmds[:0], cmds, func(c *ffcli.Command) bool { return c != nil })
|
||||
}
|
||||
|
||||
func fatalf(format string, a ...any) {
|
||||
if Fatalf != nil {
|
||||
Fatalf(format, a...)
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
@ -16,42 +18,48 @@ import (
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
syspolicyCmd = mkSyspolicyCmd
|
||||
}
|
||||
|
||||
var syspolicyArgs struct {
|
||||
json bool // JSON output mode
|
||||
}
|
||||
|
||||
var syspolicyCmd = &ffcli.Command{
|
||||
Name: "syspolicy",
|
||||
ShortHelp: "Diagnose the MDM and system policy configuration",
|
||||
LongHelp: "The 'tailscale syspolicy' command provides tools for diagnosing the MDM and system policy configuration.",
|
||||
ShortUsage: "tailscale syspolicy <subcommand>",
|
||||
UsageFunc: usageFuncNoDefaultValues,
|
||||
Subcommands: []*ffcli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
ShortUsage: "tailscale syspolicy list",
|
||||
Exec: runSysPolicyList,
|
||||
ShortHelp: "Prints effective policy settings",
|
||||
LongHelp: "The 'tailscale syspolicy list' subcommand displays the effective policy settings and their sources (e.g., MDM or environment variables).",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("syspolicy list")
|
||||
fs.BoolVar(&syspolicyArgs.json, "json", false, "output in JSON format")
|
||||
return fs
|
||||
})(),
|
||||
func mkSyspolicyCmd() *ffcli.Command {
|
||||
return &ffcli.Command{
|
||||
Name: "syspolicy",
|
||||
ShortHelp: "Diagnose the MDM and system policy configuration",
|
||||
LongHelp: "The 'tailscale syspolicy' command provides tools for diagnosing the MDM and system policy configuration.",
|
||||
ShortUsage: "tailscale syspolicy <subcommand>",
|
||||
UsageFunc: usageFuncNoDefaultValues,
|
||||
Subcommands: []*ffcli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
ShortUsage: "tailscale syspolicy list",
|
||||
Exec: runSysPolicyList,
|
||||
ShortHelp: "Prints effective policy settings",
|
||||
LongHelp: "The 'tailscale syspolicy list' subcommand displays the effective policy settings and their sources (e.g., MDM or environment variables).",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("syspolicy list")
|
||||
fs.BoolVar(&syspolicyArgs.json, "json", false, "output in JSON format")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "reload",
|
||||
ShortUsage: "tailscale syspolicy reload",
|
||||
Exec: runSysPolicyReload,
|
||||
ShortHelp: "Forces a reload of policy settings, even if no changes are detected, and prints the result",
|
||||
LongHelp: "The 'tailscale syspolicy reload' subcommand forces a reload of policy settings, even if no changes are detected, and prints the result.",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("syspolicy reload")
|
||||
fs.BoolVar(&syspolicyArgs.json, "json", false, "output in JSON format")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "reload",
|
||||
ShortUsage: "tailscale syspolicy reload",
|
||||
Exec: runSysPolicyReload,
|
||||
ShortHelp: "Forces a reload of policy settings, even if no changes are detected, and prints the result",
|
||||
LongHelp: "The 'tailscale syspolicy reload' subcommand forces a reload of policy settings, even if no changes are detected, and prints the result.",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("syspolicy reload")
|
||||
fs.BoolVar(&syspolicyArgs.json, "json", false, "output in JSON format")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func runSysPolicyList(ctx context.Context, args []string) error {
|
||||
|
@ -39,6 +39,7 @@ import (
|
||||
"tailscale.com/types/preftype"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@ -608,7 +609,7 @@ func runUp(ctx context.Context, cmd string, args []string, upArgs upArgsT) (retE
|
||||
if env.upArgs.json {
|
||||
printUpDoneJSON(ipn.NeedsMachineAuth, "")
|
||||
} 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.NoPolicyClient{})) // XXX from where?
|
||||
}
|
||||
case ipn.Running:
|
||||
// Done full authentication process
|
||||
|
@ -167,20 +167,16 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/util/set from tailscale.com/derp+
|
||||
tailscale.com/util/singleflight from tailscale.com/net/dnscache+
|
||||
tailscale.com/util/slicesx from tailscale.com/net/dns/recursive+
|
||||
tailscale.com/util/syspolicy from tailscale.com/ipn
|
||||
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/rsop from tailscale.com/util/syspolicy
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting
|
||||
tailscale.com/util/syspolicy/pkey from tailscale.com/ipn+
|
||||
tailscale.com/util/syspolicy/policyclient from tailscale.com/client/web+
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/client/tailscale+
|
||||
tailscale.com/util/testenv from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/util/truncate from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/util/usermetric from tailscale.com/health
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
W 💣 tailscale.com/util/winutil from tailscale.com/clientupdate+
|
||||
W 💣 tailscale.com/util/winutil/authenticode from tailscale.com/clientupdate
|
||||
W 💣 tailscale.com/util/winutil/gp from tailscale.com/util/syspolicy/source
|
||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
||||
tailscale.com/version from tailscale.com/client/web+
|
||||
tailscale.com/version/distro from tailscale.com/client/web+
|
||||
@ -201,7 +197,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/sha3 from crypto/internal/mlkem768+
|
||||
W 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
|
||||
golang.org/x/net/bpf from github.com/mdlayher/netlink+
|
||||
golang.org/x/net/dns/dnsmessage from net+
|
||||
golang.org/x/net/http/httpguts from net/http+
|
||||
|
@ -394,10 +394,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/set from tailscale.com/derp+
|
||||
tailscale.com/util/singleflight from tailscale.com/control/controlclient+
|
||||
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/cmd/tailscaled
|
||||
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting+
|
||||
tailscale.com/util/syspolicy/internal/loggerx from tailscale.com/util/syspolicy/internal/metrics+
|
||||
tailscale.com/util/syspolicy/internal/metrics from tailscale.com/util/syspolicy/source
|
||||
tailscale.com/util/syspolicy/pkey from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/syspolicy/policyclient from tailscale.com/client/web+
|
||||
tailscale.com/util/syspolicy/rsop from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/setting from tailscale.com/util/syspolicy+
|
||||
tailscale.com/util/syspolicy/source from tailscale.com/util/syspolicy+
|
||||
|
@ -9,6 +9,28 @@ import (
|
||||
"tailscale.com/tstest/deptest"
|
||||
)
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
"testing": "do not use testing package in production code",
|
||||
"gvisor.dev/gvisor/pkg/hostarch": "will crash on non-4K page sizes; see https://github.com/tailscale/tailscale/issues/8658",
|
||||
},
|
||||
}.Check(t)
|
||||
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
"testing": "do not use testing package in production code",
|
||||
"gvisor.dev/gvisor/pkg/hostarch": "will crash on non-4K page sizes; see https://github.com/tailscale/tailscale/issues/8658",
|
||||
"google.golang.org/protobuf/proto": "unexpected",
|
||||
"github.com/prometheus/client_golang/prometheus": "use tailscale.com/metrics in tailscaled",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitSSH(t *testing.T) {
|
||||
const msg = "unexpected with ts_omit_ssh"
|
||||
deptest.DepChecker{
|
||||
@ -28,3 +50,22 @@ func TestOmitSSH(t *testing.T) {
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitSyspolicy(t *testing.T) {
|
||||
const msg = "unexpected with ts_omit_syspolicy"
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
Tags: "ts_omit_syspolicy",
|
||||
BadDeps: map[string]string{
|
||||
"tailscale.com/util/syspolicy": msg,
|
||||
"tailscale.com/util/syspolicy/internal": msg,
|
||||
"tailscale.com/util/syspolicy/setting": msg,
|
||||
"tailscale.com/util/syspolicy/rsop": msg,
|
||||
"tailscale.com/util/syspolicy/internal/metrics": msg,
|
||||
"tailscale.com/util/syspolicy/internal/loggerx": msg,
|
||||
"tailscale.com/util/syspolicy/source": msg,
|
||||
// Only /pkey and /policyclient are allowed.
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
50
cmd/tailscaled/syspolicy.go
Normal file
50
cmd/tailscaled/syspolicy.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"tailscale.com/tsd"
|
||||
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initSyspolicy = func(sys *tsd.System) {
|
||||
sys.PolicyClient.Set(globalSyspolicy{})
|
||||
}
|
||||
}
|
||||
|
||||
// globalSyspolicy implements [policyclient.Client] using
|
||||
// the syspolicy global functions and global registrations.
|
||||
//
|
||||
// TODO: de-global-ify
|
||||
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) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) {
|
||||
return syspolicy.RegisterChangeCallback(cb)
|
||||
}
|
||||
|
||||
type PolicyChange interface {
|
||||
HasChanged(key pkey.Key) bool
|
||||
}
|
@ -334,12 +334,17 @@ func ipnServerOpts() (o serverOptions) {
|
||||
|
||||
var logPol *logpolicy.Policy
|
||||
var debugMux *http.ServeMux
|
||||
var initSyspolicy func(*tsd.System) // nil if ts_omit_syspolicy
|
||||
|
||||
func run() (err error) {
|
||||
var logf logger.Logf = log.Printf
|
||||
|
||||
sys := new(tsd.System)
|
||||
|
||||
if initSyspolicy != nil {
|
||||
initSyspolicy(sys)
|
||||
}
|
||||
|
||||
// Parse config, if specified, to fail early if it's invalid.
|
||||
var conf *conffile.Config
|
||||
if args.confFile != "" {
|
||||
@ -379,7 +384,7 @@ func run() (err error) {
|
||||
if isWinSvc {
|
||||
// Run the IPN server from the Windows service manager.
|
||||
log.Printf("Running service...")
|
||||
if err := runWindowsService(pol); err != nil {
|
||||
if err := runWindowsService(sys.PolicyClientOrDefault(), pol); err != nil {
|
||||
log.Printf("runservice: %v", err)
|
||||
}
|
||||
log.Printf("Service ended.")
|
||||
|
@ -5,10 +5,13 @@
|
||||
|
||||
package main // import "tailscale.com/cmd/tailscaled"
|
||||
|
||||
import "tailscale.com/logpolicy"
|
||||
import (
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func isWindowsService() bool { return false }
|
||||
|
||||
func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") }
|
||||
func runWindowsService(polc policyclient.Client, pol *logpolicy.Policy) error { panic("unreachable") }
|
||||
|
||||
func beWindowsSubprocess() bool { return false }
|
||||
|
@ -5,8 +5,6 @@ package main // import "tailscale.com/cmd/tailscaled"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tstest/deptest"
|
||||
)
|
||||
|
||||
func TestNothing(t *testing.T) {
|
||||
@ -14,25 +12,3 @@ func TestNothing(t *testing.T) {
|
||||
// GODEBUG=memprofilerate=1 go test -v -run=Nothing -memprofile=prof.mem
|
||||
// without any errors about no matching tests.
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
"testing": "do not use testing package in production code",
|
||||
"gvisor.dev/gvisor/pkg/hostarch": "will crash on non-4K page sizes; see https://github.com/tailscale/tailscale/issues/8658",
|
||||
},
|
||||
}.Check(t)
|
||||
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
"testing": "do not use testing package in production code",
|
||||
"gvisor.dev/gvisor/pkg/hostarch": "will crash on non-4K page sizes; see https://github.com/tailscale/tailscale/issues/8658",
|
||||
"google.golang.org/protobuf/proto": "unexpected",
|
||||
"github.com/prometheus/client_golang/prometheus": "use tailscale.com/metrics in tailscaled",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
@ -53,7 +53,8 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/logid"
|
||||
"tailscale.com/util/osdiag"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/winutil"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wf"
|
||||
@ -129,14 +130,17 @@ var syslogf logger.Logf = logger.Discard
|
||||
//
|
||||
// At this point we're still the parent process that
|
||||
// Windows started.
|
||||
func runWindowsService(pol *logpolicy.Policy) error {
|
||||
func runWindowsService(polc policyclient.Client, pol *logpolicy.Policy) error {
|
||||
if polc == nil {
|
||||
return errors.New("nil policy client")
|
||||
}
|
||||
go func() {
|
||||
logger.Logf(log.Printf).JSON(1, "SupportInfo", osdiag.SupportInfo(osdiag.LogSupportInfoReasonStartup))
|
||||
}()
|
||||
|
||||
if syslog, err := eventlog.Open(serviceName); err == nil {
|
||||
syslogf = func(format string, args ...any) {
|
||||
if logSCMInteractions, _ := syspolicy.GetBoolean(syspolicy.LogSCMInteractions, false); logSCMInteractions {
|
||||
if logSCMInteractions, _ := polc.GetBoolean(pkey.LogSCMInteractions, false); logSCMInteractions {
|
||||
syslog.Info(0, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
@ -145,11 +149,15 @@ func runWindowsService(pol *logpolicy.Policy) error {
|
||||
|
||||
syslogf("Service entering svc.Run")
|
||||
defer syslogf("Service exiting svc.Run")
|
||||
return svc.Run(serviceName, &ipnService{Policy: pol})
|
||||
return svc.Run(serviceName, &ipnService{
|
||||
Policy: pol,
|
||||
polc: polc,
|
||||
})
|
||||
}
|
||||
|
||||
type ipnService struct {
|
||||
Policy *logpolicy.Policy
|
||||
Policy *logpolicy.Policy // always non-nil
|
||||
polc policyclient.Client // always non-nil
|
||||
}
|
||||
|
||||
// Called by Windows to execute the windows service.
|
||||
@ -196,7 +204,7 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
|
||||
changes <- cmd.CurrentStatus
|
||||
case svc.SessionChange:
|
||||
syslogf("Service session change notification")
|
||||
handleSessionChange(cmd)
|
||||
handleSessionChange(service.polc, cmd)
|
||||
changes <- cmd.CurrentStatus
|
||||
case cmdUninstallWinTun:
|
||||
syslogf("Stopping tailscaled child process and uninstalling WinTun")
|
||||
@ -362,12 +370,12 @@ func beFirewallKillswitch() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func handleSessionChange(chgRequest svc.ChangeRequest) {
|
||||
func handleSessionChange(polc policyclient.Client, chgRequest svc.ChangeRequest) {
|
||||
if chgRequest.Cmd != svc.SessionChange || chgRequest.EventType != windows.WTS_SESSION_UNLOCK {
|
||||
return
|
||||
}
|
||||
|
||||
if flushDNSOnSessionUnlock, _ := syspolicy.GetBoolean(syspolicy.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock {
|
||||
if flushDNSOnSessionUnlock, _ := polc.GetBoolean(pkey.FlushDNSOnSessionUnlock, false); flushDNSOnSessionUnlock {
|
||||
log.Printf("Received WTS_SESSION_UNLOCK event, initiating DNS flush.")
|
||||
go func() {
|
||||
err := dns.Flush()
|
||||
|
@ -6,6 +6,7 @@ package controlclient
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
@ -53,7 +54,8 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/singleflight"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/systemd"
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/util/zstdframe"
|
||||
@ -76,6 +78,7 @@ type Direct struct {
|
||||
debugFlags []string
|
||||
skipIPForwardingCheck bool
|
||||
pinger Pinger
|
||||
polc policyclient.Client // always non-nil
|
||||
popBrowser func(url string) // or nil
|
||||
c2nHandler http.Handler // or nil
|
||||
onClientVersion func(*tailcfg.ClientVersion) // or nil
|
||||
@ -124,9 +127,10 @@ type Options struct {
|
||||
Hostinfo *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
|
||||
DiscoPublicKey key.DiscoPublic
|
||||
Logf logger.Logf
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
|
||||
DebugFlags []string // debug settings to send to control
|
||||
PolicyClient policyclient.Client // or nil for none
|
||||
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
|
||||
NoiseTestClient *http.Client // optional HTTP client to use for noise RPCs (tests only)
|
||||
DebugFlags []string // debug settings to send to control
|
||||
HealthTracker *health.Tracker
|
||||
PopBrowserURL func(url string) // optional func to open browser
|
||||
OnClientVersion func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
|
||||
@ -296,6 +300,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
health: opts.HealthTracker,
|
||||
skipIPForwardingCheck: opts.SkipIPForwardingCheck,
|
||||
pinger: opts.Pinger,
|
||||
polc: cmp.Or(opts.PolicyClient, policyclient.Client(policyclient.NoPolicyClient{})),
|
||||
popBrowser: opts.PopBrowserURL,
|
||||
onClientVersion: opts.OnClientVersion,
|
||||
onTailnetDefaultAutoUpdate: opts.OnTailnetDefaultAutoUpdate,
|
||||
@ -606,7 +611,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
return regen, opt.URL, nil, err
|
||||
}
|
||||
|
||||
tailnet, err := syspolicy.GetString(syspolicy.Tailnet, "")
|
||||
tailnet, err := c.polc.GetString(pkey.Tailnet, "")
|
||||
if err != nil {
|
||||
c.logf("unable to provide Tailnet field in register request. err: %v", err)
|
||||
}
|
||||
@ -636,7 +641,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
AuthKey: authKey,
|
||||
}
|
||||
}
|
||||
err = signRegisterRequest(&request, c.serverURL, c.serverLegacyKey, machinePrivKey.Public())
|
||||
err = signRegisterRequest(c.polc, &request, c.serverURL, c.serverLegacyKey, machinePrivKey.Public())
|
||||
if err != nil {
|
||||
// If signing failed, clear all related fields
|
||||
request.SignatureType = tailcfg.SignatureNone
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
"github.com/tailscale/certstore"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// getMachineCertificateSubject returns the exact name of a Subject that needs
|
||||
@ -30,8 +31,8 @@ import (
|
||||
// each RegisterRequest will be unsigned.
|
||||
//
|
||||
// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
|
||||
func getMachineCertificateSubject() string {
|
||||
machineCertSubject, _ := syspolicy.GetString(syspolicy.MachineCertificateSubject, "")
|
||||
func getMachineCertificateSubject(polc policyclient.Client) string {
|
||||
machineCertSubject, _ := polc.GetString(pkey.MachineCertificateSubject, "")
|
||||
return machineCertSubject
|
||||
}
|
||||
|
||||
@ -136,7 +137,7 @@ func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x5
|
||||
// using that identity's public key. In addition to the signature, the full
|
||||
// certificate chain is included so that the control server can validate the
|
||||
// certificate from a copy of the root CA's certificate.
|
||||
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) (err error) {
|
||||
func signRegisterRequest(polc policyclient.Client, req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("signRegisterRequest: %w", err)
|
||||
@ -147,7 +148,7 @@ func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverP
|
||||
return errBadRequest
|
||||
}
|
||||
|
||||
machineCertificateSubject := getMachineCertificateSubject()
|
||||
machineCertificateSubject := getMachineCertificateSubject(polc)
|
||||
if machineCertificateSubject == "" {
|
||||
return errCertificateNotConfigured
|
||||
}
|
||||
|
@ -8,9 +8,10 @@ package controlclient
|
||||
import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// signRegisterRequest on non-supported platforms always returns errNoCertStore.
|
||||
func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) error {
|
||||
func signRegisterRequest(polc policyclient.Client, req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey key.MachinePublic) error {
|
||||
return errNoCertStore
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/goroutines"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@ -335,7 +335,7 @@ func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http
|
||||
// this will first check syspolicy, MDM settings like Registry
|
||||
// on Windows or defaults on macOS. If they are not set, it falls
|
||||
// back to the cli-flag, `--posture-checking`.
|
||||
choice, err := syspolicy.GetPreferenceOption(syspolicy.PostureChecking)
|
||||
choice, err := b.polc.GetBoolean(pkey.PostureChecking, true)
|
||||
if err != nil {
|
||||
b.logf(
|
||||
"c2n: failed to read PostureChecking from syspolicy, returning default from CLI: %s; got error: %s",
|
||||
@ -344,8 +344,8 @@ func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http
|
||||
)
|
||||
}
|
||||
|
||||
if choice.ShouldEnable(b.Prefs().PostureChecking()) {
|
||||
res.SerialNumbers, err = posture.GetSerialNumbers(b.logf)
|
||||
if choice {
|
||||
res.SerialNumbers, err = posture.GetSerialNumbers(b.polc, b.logf)
|
||||
if err != nil {
|
||||
b.logf("c2n: GetSerialNumbers returned error: %v", err)
|
||||
}
|
||||
|
@ -107,8 +107,8 @@ import (
|
||||
"tailscale.com/util/rands"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/slicesx"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/systemd"
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/util/usermetric"
|
||||
@ -186,7 +186,8 @@ type LocalBackend struct {
|
||||
keyLogf logger.Logf // for printing list of peers on change
|
||||
statsLogf logger.Logf // for printing peers stats on change
|
||||
sys *tsd.System
|
||||
health *health.Tracker // always non-nil
|
||||
polc policyclient.Client // always non-nil
|
||||
health *health.Tracker // always non-nil
|
||||
metrics metrics
|
||||
e wgengine.Engine // non-nil; TODO(bradfitz): remove; use sys
|
||||
store ipn.StateStore // non-nil; TODO(bradfitz): remove; use sys
|
||||
@ -362,7 +363,7 @@ type LocalBackend struct {
|
||||
lastSuggestedExitNode tailcfg.StableNodeID
|
||||
|
||||
// allowedSuggestedExitNodes is a set of exit nodes permitted by the most recent
|
||||
// [syspolicy.AllowedSuggestedExitNodes] value. The allowedSuggestedExitNodesMu
|
||||
// [pkey.AllowedSuggestedExitNodes] value. The allowedSuggestedExitNodesMu
|
||||
// mutex guards access to this set.
|
||||
allowedSuggestedExitNodesMu sync.Mutex
|
||||
allowedSuggestedExitNodes set.Set[tailcfg.StableNodeID]
|
||||
@ -472,6 +473,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
||||
keyLogf: logger.LogOnChange(logf, 5*time.Minute, clock.Now),
|
||||
statsLogf: logger.LogOnChange(logf, 5*time.Minute, clock.Now),
|
||||
sys: sys,
|
||||
polc: sys.PolicyClientOrDefault(),
|
||||
health: sys.HealthTracker(),
|
||||
metrics: m,
|
||||
e: e,
|
||||
@ -602,8 +604,9 @@ func (b *LocalBackend) SetComponentDebugLogging(component string, until time.Tim
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "syspolicy":
|
||||
setEnabled = syspolicy.SetDebugLoggingEnabled
|
||||
setEnabled = b.polc.SetDebugLoggingEnabled
|
||||
}
|
||||
if setEnabled == nil || !slices.Contains(ipn.DebuggableComponents, component) {
|
||||
return fmt.Errorf("unknown component %q", component)
|
||||
@ -849,7 +852,7 @@ func (b *LocalBackend) linkChange(delta *netmon.ChangeDelta) {
|
||||
hadPAC := b.prevIfState.HasPAC()
|
||||
b.prevIfState = ifst
|
||||
b.pauseOrResumeControlClientLocked()
|
||||
if delta.Major && shouldAutoExitNode() {
|
||||
if delta.Major && shouldAutoExitNode(b.polc) {
|
||||
b.refreshAutoExitNode = true
|
||||
}
|
||||
|
||||
@ -1496,7 +1499,7 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
// future "tailscale up" to start checking for
|
||||
// implicit setting reverts, which it doesn't do when
|
||||
// ControlURL is blank.
|
||||
prefs.ControlURL = prefs.ControlURLOrDefault()
|
||||
prefs.ControlURL = prefs.ControlURLOrDefault(b.polc)
|
||||
prefsChanged = true
|
||||
}
|
||||
if st.Persist.Valid() {
|
||||
@ -1521,14 +1524,14 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
prefsChanged = true
|
||||
}
|
||||
}
|
||||
if shouldAutoExitNode() {
|
||||
if shouldAutoExitNode(b.polc) {
|
||||
// Re-evaluate exit node suggestion in case circumstances have changed.
|
||||
_, err := b.suggestExitNodeLocked(curNetMap)
|
||||
if err != nil && !errors.Is(err, ErrNoPreferredDERP) {
|
||||
b.logf("SetControlClientStatus failed to select auto exit node: %v", err)
|
||||
}
|
||||
}
|
||||
if applySysPolicy(prefs, b.lastSuggestedExitNode) {
|
||||
if applySysPolicy(b.polc, prefs, b.lastSuggestedExitNode) {
|
||||
prefsChanged = true
|
||||
}
|
||||
if setExitNodeID(prefs, curNetMap) {
|
||||
@ -1645,51 +1648,51 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
|
||||
}
|
||||
|
||||
type preferencePolicyInfo struct {
|
||||
key syspolicy.Key
|
||||
key pkey.Key
|
||||
get func(ipn.PrefsView) bool
|
||||
set func(*ipn.Prefs, bool)
|
||||
}
|
||||
|
||||
var preferencePolicies = []preferencePolicyInfo{
|
||||
{
|
||||
key: syspolicy.EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
// Allow Incoming (used by the UI) is the negation of ShieldsUp (used by the
|
||||
// backend), so this has to convert between the two conventions.
|
||||
get: func(p ipn.PrefsView) bool { return !p.ShieldsUp() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.ShieldsUp = !v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.EnableServerMode,
|
||||
key: pkey.EnableServerMode,
|
||||
get: func(p ipn.PrefsView) bool { return p.ForceDaemon() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.ForceDaemon = v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.ExitNodeAllowLANAccess,
|
||||
key: pkey.ExitNodeAllowLANAccess,
|
||||
get: func(p ipn.PrefsView) bool { return p.ExitNodeAllowLANAccess() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.ExitNodeAllowLANAccess = v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.EnableTailscaleDNS,
|
||||
key: pkey.EnableTailscaleDNS,
|
||||
get: func(p ipn.PrefsView) bool { return p.CorpDNS() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.CorpDNS = v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.EnableTailscaleSubnets,
|
||||
key: pkey.EnableTailscaleSubnets,
|
||||
get: func(p ipn.PrefsView) bool { return p.RouteAll() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.RouteAll = v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.CheckUpdates,
|
||||
key: pkey.CheckUpdates,
|
||||
get: func(p ipn.PrefsView) bool { return p.AutoUpdate().Check },
|
||||
set: func(p *ipn.Prefs, v bool) { p.AutoUpdate.Check = v },
|
||||
},
|
||||
{
|
||||
key: syspolicy.ApplyUpdates,
|
||||
key: pkey.ApplyUpdates,
|
||||
get: func(p ipn.PrefsView) bool { v, _ := p.AutoUpdate().Apply.Get(); return v },
|
||||
set: func(p *ipn.Prefs, v bool) { p.AutoUpdate.Apply.Set(v) },
|
||||
},
|
||||
{
|
||||
key: syspolicy.EnableRunExitNode,
|
||||
key: pkey.EnableRunExitNode,
|
||||
get: func(p ipn.PrefsView) bool { return p.AdvertisesExitNode() },
|
||||
set: func(p *ipn.Prefs, v bool) { p.SetAdvertiseExitNode(v) },
|
||||
},
|
||||
@ -1697,14 +1700,14 @@ var preferencePolicies = []preferencePolicyInfo{
|
||||
|
||||
// applySysPolicy overwrites configured preferences with policies that may be
|
||||
// configured by the system administrator in an OS-specific way.
|
||||
func applySysPolicy(prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID) (anyChange bool) {
|
||||
if controlURL, err := syspolicy.GetString(syspolicy.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL {
|
||||
func applySysPolicy(polc policyclient.Client, prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID) (anyChange bool) {
|
||||
if controlURL, err := polc.GetString(pkey.ControlURL, prefs.ControlURL); err == nil && prefs.ControlURL != controlURL {
|
||||
prefs.ControlURL = controlURL
|
||||
anyChange = true
|
||||
}
|
||||
|
||||
const sentinel = "HostnameDefaultValue"
|
||||
hostnameFromPolicy, _ := syspolicy.GetString(syspolicy.Hostname, sentinel)
|
||||
hostnameFromPolicy, _ := polc.GetString(pkey.Hostname, sentinel)
|
||||
switch hostnameFromPolicy {
|
||||
case sentinel:
|
||||
// An empty string for this policy value means that the admin wants to delete
|
||||
@ -1734,9 +1737,9 @@ func applySysPolicy(prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID
|
||||
}
|
||||
}
|
||||
|
||||
if exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, ""); exitNodeIDStr != "" {
|
||||
if exitNodeIDStr, _ := polc.GetString(pkey.ExitNodeID, ""); exitNodeIDStr != "" {
|
||||
exitNodeID := tailcfg.StableNodeID(exitNodeIDStr)
|
||||
if shouldAutoExitNode() && lastSuggestedExitNode != "" {
|
||||
if shouldAutoExitNode(polc) && lastSuggestedExitNode != "" {
|
||||
exitNodeID = lastSuggestedExitNode
|
||||
}
|
||||
// Note: when exitNodeIDStr == "auto" && lastSuggestedExitNode == "",
|
||||
@ -1748,7 +1751,7 @@ func applySysPolicy(prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID
|
||||
}
|
||||
prefs.ExitNodeID = exitNodeID
|
||||
prefs.ExitNodeIP = netip.Addr{}
|
||||
} else if exitNodeIPStr, _ := syspolicy.GetString(syspolicy.ExitNodeIP, ""); exitNodeIPStr != "" {
|
||||
} else if exitNodeIPStr, _ := polc.GetString(pkey.ExitNodeIP, ""); exitNodeIPStr != "" {
|
||||
exitNodeIP, err := netip.ParseAddr(exitNodeIPStr)
|
||||
if exitNodeIP.IsValid() && err == nil {
|
||||
if prefs.ExitNodeID != "" || prefs.ExitNodeIP != exitNodeIP {
|
||||
@ -1760,9 +1763,8 @@ func applySysPolicy(prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID
|
||||
}
|
||||
|
||||
for _, opt := range preferencePolicies {
|
||||
if po, err := syspolicy.GetPreferenceOption(opt.key); err == nil {
|
||||
curVal := opt.get(prefs.View())
|
||||
newVal := po.ShouldEnable(curVal)
|
||||
curVal := opt.get(prefs.View())
|
||||
if newVal, err := polc.GetBoolean(opt.key, curVal); err == nil {
|
||||
if curVal != newVal {
|
||||
opt.set(prefs, newVal)
|
||||
anyChange = true
|
||||
@ -1776,7 +1778,7 @@ func applySysPolicy(prefs *ipn.Prefs, lastSuggestedExitNode tailcfg.StableNodeID
|
||||
// registerSysPolicyWatch subscribes to syspolicy change notifications
|
||||
// and immediately applies the effective syspolicy settings to the current profile.
|
||||
func (b *LocalBackend) registerSysPolicyWatch() (unregister func(), err error) {
|
||||
if unregister, err = syspolicy.RegisterChangeCallback(b.sysPolicyChanged); err != nil {
|
||||
if unregister, err = b.polc.RegisterChangeCallback(b.sysPolicyChanged); err != nil {
|
||||
return nil, fmt.Errorf("syspolicy: LocalBacked failed to register policy change callback: %v", err)
|
||||
}
|
||||
if prefs, anyChange := b.applySysPolicy(); anyChange {
|
||||
@ -1793,7 +1795,7 @@ func (b *LocalBackend) registerSysPolicyWatch() (unregister func(), err error) {
|
||||
func (b *LocalBackend) applySysPolicy() (_ ipn.PrefsView, anyChange bool) {
|
||||
unlock := b.lockAndGetUnlock()
|
||||
prefs := b.pm.CurrentPrefs().AsStruct()
|
||||
if !applySysPolicy(prefs, b.lastSuggestedExitNode) {
|
||||
if !applySysPolicy(b.polc, prefs, b.lastSuggestedExitNode) {
|
||||
unlock.UnlockEarly()
|
||||
return prefs.View(), false
|
||||
}
|
||||
@ -1802,8 +1804,8 @@ func (b *LocalBackend) applySysPolicy() (_ ipn.PrefsView, anyChange bool) {
|
||||
|
||||
// sysPolicyChanged is a callback triggered by syspolicy when it detects
|
||||
// a change in one or more syspolicy settings.
|
||||
func (b *LocalBackend) sysPolicyChanged(policy *rsop.PolicyChange) {
|
||||
if policy.HasChanged(syspolicy.AllowedSuggestedExitNodes) {
|
||||
func (b *LocalBackend) sysPolicyChanged(policy policyclient.PolicyChange) {
|
||||
if policy.HasChanged(pkey.AllowedSuggestedExitNodes) {
|
||||
b.refreshAllowedSuggestions()
|
||||
// Re-evaluate exit node suggestion now that the policy setting has changed.
|
||||
b.mu.Lock()
|
||||
@ -1812,7 +1814,7 @@ func (b *LocalBackend) sysPolicyChanged(policy *rsop.PolicyChange) {
|
||||
if err != nil && !errors.Is(err, ErrNoPreferredDERP) {
|
||||
b.logf("failed to select auto exit node: %v", err)
|
||||
}
|
||||
// If [syspolicy.ExitNodeID] is set to `auto:any`, the suggested exit node ID
|
||||
// If [pkey.ExitNodeID] is set to `auto:any`, the suggested exit node ID
|
||||
// will be used when [applySysPolicy] updates the current profile's prefs.
|
||||
}
|
||||
|
||||
@ -1916,7 +1918,7 @@ func (b *LocalBackend) updateNetmapDeltaLocked(muts []netmap.NodeMutation) (hand
|
||||
|
||||
// If our exit node went offline, we need to schedule picking
|
||||
// a new one.
|
||||
if mo, ok := m.(netmap.NodeMutationOnline); ok && !mo.Online && n.StableID == b.pm.prefs.ExitNodeID() && shouldAutoExitNode() {
|
||||
if mo, ok := m.(netmap.NodeMutationOnline); ok && !mo.Online && n.StableID == b.pm.prefs.ExitNodeID() && shouldAutoExitNode(b.polc) {
|
||||
b.goTracker.Go(b.pickNewAutoExitNode)
|
||||
}
|
||||
}
|
||||
@ -2149,7 +2151,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
}
|
||||
|
||||
if b.state != ipn.Running && b.conf == nil && opts.AuthKey == "" {
|
||||
sysak, _ := syspolicy.GetString(syspolicy.AuthKey, "")
|
||||
sysak, _ := b.polc.GetString(pkey.AuthKey, "")
|
||||
if sysak != "" {
|
||||
b.logf("Start: setting opts.AuthKey by syspolicy, len=%v", len(sysak))
|
||||
opts.AuthKey = strings.TrimSpace(sysak)
|
||||
@ -2207,7 +2209,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
||||
|
||||
loggedOut := prefs.LoggedOut()
|
||||
|
||||
serverURL := prefs.ControlURLOrDefault()
|
||||
serverURL := prefs.ControlURLOrDefault(b.polc)
|
||||
if inServerMode := prefs.ForceDaemon(); inServerMode || runtime.GOOS == "windows" {
|
||||
b.logf("Start: serverMode=%v", inServerMode)
|
||||
}
|
||||
@ -3192,7 +3194,7 @@ func (b *LocalBackend) validPopBrowserURL(urlStr string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
serverURL := b.Prefs().ControlURLOrDefault()
|
||||
serverURL := b.Prefs().ControlURLOrDefault(b.polc)
|
||||
if ipn.IsLoginServerSynonym(serverURL) {
|
||||
// When connected to the official Tailscale control plane, only allow
|
||||
// URLs from tailscale.com or its subdomains.
|
||||
@ -3793,7 +3795,7 @@ func (b *LocalBackend) isDefaultServerLocked() bool {
|
||||
if !prefs.Valid() {
|
||||
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{
|
||||
@ -4010,7 +4012,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(newp *ipn.Prefs, unlock unlockOnce)
|
||||
// applySysPolicyToPrefsLocked returns whether it updated newp,
|
||||
// but everything in this function treats b.prefs as completely new
|
||||
// anyway, so its return value can be ignored here.
|
||||
applySysPolicy(newp, b.lastSuggestedExitNode)
|
||||
applySysPolicy(b.polc, newp, b.lastSuggestedExitNode)
|
||||
// setExitNodeID does likewise. No-op if no exit node resolution is needed.
|
||||
setExitNodeID(newp, netMap)
|
||||
// We do this to avoid holding the lock while doing everything else.
|
||||
@ -4356,6 +4358,33 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i
|
||||
b.appConnector.UpdateDomainsAndRoutes(domains, routes)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) readvertiseAppConnectorRoutes() {
|
||||
var domainRoutes map[string][]netip.Addr
|
||||
b.mu.Lock()
|
||||
if b.appConnector != nil {
|
||||
domainRoutes = b.appConnector.DomainRoutes()
|
||||
}
|
||||
b.mu.Unlock()
|
||||
if domainRoutes == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Re-advertise the stored routes, in case stored state got out of
|
||||
// sync with previously advertised routes in prefs.
|
||||
var prefixes []netip.Prefix
|
||||
for _, ips := range domainRoutes {
|
||||
for _, ip := range ips {
|
||||
prefixes = append(prefixes, netip.PrefixFrom(ip, ip.BitLen()))
|
||||
}
|
||||
}
|
||||
// Note: AdvertiseRoute will trim routes that are already
|
||||
// advertised, so if everything is already being advertised this is
|
||||
// a noop.
|
||||
if err := b.AdvertiseRoute(prefixes...); err != nil {
|
||||
b.logf("error advertising stored app connector routes: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// authReconfig pushes a new configuration into wgengine, if engine
|
||||
// updates are not currently blocked, based on the cached netmap and
|
||||
// user prefs.
|
||||
@ -4434,6 +4463,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
}
|
||||
|
||||
b.initPeerAPIListener()
|
||||
b.readvertiseAppConnectorRoutes()
|
||||
}
|
||||
|
||||
// shouldUseOneCGNATRoute reports whether we should prefer to make one big
|
||||
@ -5095,7 +5125,7 @@ func (b *LocalBackend) enterStateLockedOnEntry(newState ipn.State, unlock unlock
|
||||
// Some temporary (2024-05-05) debugging code to help us catch
|
||||
// https://github.com/tailscale/tailscale/issues/11962 in the act.
|
||||
if prefs.WantRunning() &&
|
||||
prefs.ControlURLOrDefault() == ipn.DefaultControlURL &&
|
||||
prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL &&
|
||||
envknob.Bool("TS_PANIC_IF_HIT_MAIN_CONTROL") {
|
||||
panic("[unexpected] use of main control server in integration test")
|
||||
}
|
||||
@ -6864,14 +6894,14 @@ func (b *LocalBackend) SwitchProfile(profile ipn.ProfileID) error {
|
||||
unlock := b.lockAndGetUnlock()
|
||||
defer unlock()
|
||||
|
||||
oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault()
|
||||
oldControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc)
|
||||
if err := b.pm.SwitchProfile(profile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// As an optimization, only reset the dialPlan if the control URL
|
||||
// changed; we treat an empty URL as "unknown" and always reset.
|
||||
newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault()
|
||||
newControlURL := b.pm.CurrentPrefs().ControlURLOrDefault(b.polc)
|
||||
if oldControlURL != newControlURL || oldControlURL == "" || newControlURL == "" {
|
||||
b.resetDialPlan()
|
||||
}
|
||||
@ -7176,7 +7206,7 @@ var ErrDisallowedAutoRoute = errors.New("route is not allowed")
|
||||
// If the route is disallowed, ErrDisallowedAutoRoute is returned.
|
||||
func (b *LocalBackend) AdvertiseRoute(ipps ...netip.Prefix) error {
|
||||
finalRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
|
||||
newRoutes := false
|
||||
var newRoutes []netip.Prefix
|
||||
|
||||
for _, ipp := range ipps {
|
||||
if !allowedAutoRoute(ipp) {
|
||||
@ -7192,13 +7222,14 @@ func (b *LocalBackend) AdvertiseRoute(ipps ...netip.Prefix) error {
|
||||
}
|
||||
|
||||
finalRoutes = append(finalRoutes, ipp)
|
||||
newRoutes = true
|
||||
newRoutes = append(newRoutes, ipp)
|
||||
}
|
||||
|
||||
if !newRoutes {
|
||||
if len(newRoutes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.logf("advertising new app connector routes: %v", newRoutes)
|
||||
_, err := b.EditPrefs(&ipn.MaskedPrefs{
|
||||
Prefs: ipn.Prefs{
|
||||
AdvertiseRoutes: finalRoutes,
|
||||
@ -7370,7 +7401,7 @@ func (b *LocalBackend) SuggestExitNode() (response apitype.ExitNodeSuggestionRes
|
||||
}
|
||||
|
||||
// getAllowedSuggestions returns a set of exit nodes permitted by the most recent
|
||||
// [syspolicy.AllowedSuggestedExitNodes] value. Callers must not mutate the returned set.
|
||||
// [pkey.AllowedSuggestedExitNodes] value. Callers must not mutate the returned set.
|
||||
func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
||||
b.allowedSuggestedExitNodesMu.Lock()
|
||||
defer b.allowedSuggestedExitNodesMu.Unlock()
|
||||
@ -7378,11 +7409,11 @@ func (b *LocalBackend) getAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
||||
}
|
||||
|
||||
// refreshAllowedSuggestions rebuilds the set of permitted exit nodes
|
||||
// from the current [syspolicy.AllowedSuggestedExitNodes] value.
|
||||
// from the current [pkey.AllowedSuggestedExitNodes] value.
|
||||
func (b *LocalBackend) refreshAllowedSuggestions() {
|
||||
b.allowedSuggestedExitNodesMu.Lock()
|
||||
defer b.allowedSuggestedExitNodesMu.Unlock()
|
||||
b.allowedSuggestedExitNodes = fillAllowedSuggestions()
|
||||
b.allowedSuggestedExitNodes = fillAllowedSuggestions(b.polc)
|
||||
}
|
||||
|
||||
// selectRegionFunc returns a DERP region from the slice of candidate regions.
|
||||
@ -7394,10 +7425,10 @@ type selectRegionFunc func(views.Slice[int]) int
|
||||
// choice.
|
||||
type selectNodeFunc func(nodes views.Slice[tailcfg.NodeView], last tailcfg.StableNodeID) tailcfg.NodeView
|
||||
|
||||
func fillAllowedSuggestions() set.Set[tailcfg.StableNodeID] {
|
||||
nodes, err := syspolicy.GetStringArray(syspolicy.AllowedSuggestedExitNodes, nil)
|
||||
func fillAllowedSuggestions(polc policyclient.Client) set.Set[tailcfg.StableNodeID] {
|
||||
nodes, err := polc.GetStringArray(pkey.AllowedSuggestedExitNodes, nil)
|
||||
if err != nil {
|
||||
log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", syspolicy.AllowedSuggestedExitNodes, err)
|
||||
log.Printf("fillAllowedSuggestions: unable to look up %q policy: %v", pkey.AllowedSuggestedExitNodes, err)
|
||||
return nil
|
||||
}
|
||||
if nodes == nil {
|
||||
@ -7614,8 +7645,8 @@ func longLatDistance(fromLat, fromLong, toLat, toLong float64) float64 {
|
||||
}
|
||||
|
||||
// shouldAutoExitNode checks for the auto exit node MDM policy.
|
||||
func shouldAutoExitNode() bool {
|
||||
exitNodeIDStr, _ := syspolicy.GetString(syspolicy.ExitNodeID, "")
|
||||
func shouldAutoExitNode(polc policyclient.Client) bool {
|
||||
exitNodeIDStr, _ := polc.GetString(pkey.ExitNodeID, "")
|
||||
return exitNodeIDStr == "auto:any"
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstest/deptest"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@ -56,6 +57,8 @@ import (
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
"tailscale.com/wgengine"
|
||||
@ -1786,15 +1789,19 @@ func TestSetExitNodeIDPolicy(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Skip("XXX finish updating this test")
|
||||
|
||||
b := newTestBackend(t)
|
||||
|
||||
policyStore := source.NewTestStore(t)
|
||||
policyStore := source.NewTestStore(t) // XXX: move this to its own test-only package
|
||||
if test.exitNodeIDKey {
|
||||
policyStore.SetStrings(source.TestSettingOf(syspolicy.ExitNodeID, test.exitNodeID))
|
||||
policyStore.SetStrings(source.TestSettingOf(pkey.ExitNodeID, test.exitNodeID))
|
||||
}
|
||||
if test.exitNodeIPKey {
|
||||
policyStore.SetStrings(source.TestSettingOf(syspolicy.ExitNodeIP, test.exitNodeIP))
|
||||
policyStore.SetStrings(source.TestSettingOf(pkey.ExitNodeIP, test.exitNodeIP))
|
||||
}
|
||||
// XXX TODO: update b.polc instead to have a policy client just for this backend, don't use global variables
|
||||
// and MustRegisterStoreForTest
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
|
||||
if test.nm == nil {
|
||||
@ -1810,7 +1817,7 @@ func TestSetExitNodeIDPolicy(t *testing.T) {
|
||||
b.lastSuggestedExitNode = test.lastSuggestedExitNode
|
||||
|
||||
prefs := b.pm.prefs.AsStruct()
|
||||
if changed := applySysPolicy(prefs, test.lastSuggestedExitNode) || setExitNodeID(prefs, test.nm); changed != test.prefsChanged {
|
||||
if changed := applySysPolicy(b.polc, prefs, test.lastSuggestedExitNode) || setExitNodeID(prefs, test.nm); changed != test.prefsChanged {
|
||||
t.Errorf("wanted prefs changed %v, got prefs changed %v", test.prefsChanged, changed)
|
||||
}
|
||||
|
||||
@ -1925,7 +1932,7 @@ func TestUpdateNetmapDeltaAutoExitNode(t *testing.T) {
|
||||
|
||||
syspolicy.RegisterWellKnownSettingsForTest(t)
|
||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
||||
syspolicy.ExitNodeID, "auto:any",
|
||||
pkey.ExitNodeID, "auto:any",
|
||||
))
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
|
||||
@ -2011,7 +2018,7 @@ func TestAutoExitNodeSetNetInfoCallback(t *testing.T) {
|
||||
b.cc = cc
|
||||
syspolicy.RegisterWellKnownSettingsForTest(t)
|
||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
||||
syspolicy.ExitNodeID, "auto:any",
|
||||
pkey.ExitNodeID, "auto:any",
|
||||
))
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
peer1 := makePeer(1, withCap(26), withDERP(3), withSuggest(), withExitRoutes())
|
||||
@ -2120,7 +2127,7 @@ func TestSetControlClientStatusAutoExitNode(t *testing.T) {
|
||||
b := newTestLocalBackend(t)
|
||||
syspolicy.RegisterWellKnownSettingsForTest(t)
|
||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
||||
syspolicy.ExitNodeID, "auto:any",
|
||||
pkey.ExitNodeID, "auto:any",
|
||||
))
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
b.netMap = nm
|
||||
@ -2149,7 +2156,7 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
prefs ipn.Prefs
|
||||
wantPrefs ipn.Prefs
|
||||
wantAnyChange bool
|
||||
stringPolicies map[syspolicy.Key]string
|
||||
stringPolicies map[pkey.Key]string
|
||||
}{
|
||||
{
|
||||
name: "empty prefs without policies",
|
||||
@ -2184,13 +2191,13 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
RouteAll: true,
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ControlURL: "1",
|
||||
syspolicy.EnableIncomingConnections: "never",
|
||||
syspolicy.EnableServerMode: "always",
|
||||
syspolicy.ExitNodeAllowLANAccess: "always",
|
||||
syspolicy.EnableTailscaleDNS: "always",
|
||||
syspolicy.EnableTailscaleSubnets: "always",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ControlURL: "1",
|
||||
pkey.EnableIncomingConnections: "never",
|
||||
pkey.EnableServerMode: "always",
|
||||
pkey.ExitNodeAllowLANAccess: "always",
|
||||
pkey.EnableTailscaleDNS: "always",
|
||||
pkey.EnableTailscaleSubnets: "always",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2205,13 +2212,13 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
ShieldsUp: true,
|
||||
ForceDaemon: true,
|
||||
},
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ControlURL: "1",
|
||||
syspolicy.EnableIncomingConnections: "never",
|
||||
syspolicy.EnableServerMode: "always",
|
||||
syspolicy.ExitNodeAllowLANAccess: "never",
|
||||
syspolicy.EnableTailscaleDNS: "never",
|
||||
syspolicy.EnableTailscaleSubnets: "never",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ControlURL: "1",
|
||||
pkey.EnableIncomingConnections: "never",
|
||||
pkey.EnableServerMode: "always",
|
||||
pkey.ExitNodeAllowLANAccess: "never",
|
||||
pkey.EnableTailscaleDNS: "never",
|
||||
pkey.EnableTailscaleSubnets: "never",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2233,13 +2240,13 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
RouteAll: true,
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ControlURL: "2",
|
||||
syspolicy.EnableIncomingConnections: "always",
|
||||
syspolicy.EnableServerMode: "never",
|
||||
syspolicy.ExitNodeAllowLANAccess: "always",
|
||||
syspolicy.EnableTailscaleDNS: "never",
|
||||
syspolicy.EnableTailscaleSubnets: "always",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ControlURL: "2",
|
||||
pkey.EnableIncomingConnections: "always",
|
||||
pkey.EnableServerMode: "never",
|
||||
pkey.ExitNodeAllowLANAccess: "always",
|
||||
pkey.EnableTailscaleDNS: "never",
|
||||
pkey.EnableTailscaleSubnets: "always",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2260,12 +2267,12 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
CorpDNS: true,
|
||||
RouteAll: true,
|
||||
},
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.EnableIncomingConnections: "user-decides",
|
||||
syspolicy.EnableServerMode: "user-decides",
|
||||
syspolicy.ExitNodeAllowLANAccess: "user-decides",
|
||||
syspolicy.EnableTailscaleDNS: "user-decides",
|
||||
syspolicy.EnableTailscaleSubnets: "user-decides",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.EnableIncomingConnections: "user-decides",
|
||||
pkey.EnableServerMode: "user-decides",
|
||||
pkey.ExitNodeAllowLANAccess: "user-decides",
|
||||
pkey.EnableTailscaleDNS: "user-decides",
|
||||
pkey.EnableTailscaleSubnets: "user-decides",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2274,8 +2281,8 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
ControlURL: "set",
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ControlURL: "set",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ControlURL: "set",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2293,8 +2300,8 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ApplyUpdates: "always",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ApplyUpdates: "always",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2312,8 +2319,8 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.ApplyUpdates: "never",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.ApplyUpdates: "never",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2331,8 +2338,8 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.CheckUpdates: "always",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.CheckUpdates: "always",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2350,8 +2357,8 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantAnyChange: true,
|
||||
stringPolicies: map[syspolicy.Key]string{
|
||||
syspolicy.CheckUpdates: "never",
|
||||
stringPolicies: map[pkey.Key]string{
|
||||
pkey.CheckUpdates: "never",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -2370,7 +2377,9 @@ func TestApplySysPolicy(t *testing.T) {
|
||||
t.Run("unit", func(t *testing.T) {
|
||||
prefs := tt.prefs.Clone()
|
||||
|
||||
gotAnyChange := applySysPolicy(prefs, "")
|
||||
var polc policyclient.Client = nil // XXX TODO
|
||||
t.Skip("XXXX finish", prefs)
|
||||
gotAnyChange := applySysPolicy(polc, prefs, "")
|
||||
|
||||
if gotAnyChange && prefs.Equals(&tt.prefs) {
|
||||
t.Errorf("anyChange but prefs is unchanged: %v", prefs.Pretty())
|
||||
@ -2518,7 +2527,9 @@ func TestPreferencePolicyInfo(t *testing.T) {
|
||||
prefs := defaultPrefs.AsStruct()
|
||||
pp.set(prefs, tt.initialValue)
|
||||
|
||||
gotAnyChange := applySysPolicy(prefs, "")
|
||||
var polc policyclient.Client = nil // XXX TODO
|
||||
t.Skip("XXXX finish")
|
||||
gotAnyChange := applySysPolicy(polc, prefs, "")
|
||||
|
||||
if gotAnyChange != tt.wantChange {
|
||||
t.Errorf("anyChange=%v, want %v", gotAnyChange, tt.wantChange)
|
||||
@ -3768,11 +3779,12 @@ func TestShouldAutoExitNode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
||||
syspolicy.ExitNodeID, tt.exitNodeIDPolicyValue,
|
||||
pkey.ExitNodeID, tt.exitNodeIDPolicyValue,
|
||||
))
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
|
||||
got := shouldAutoExitNode()
|
||||
var polc policyclient.Client = nil // XXX TODO
|
||||
got := shouldAutoExitNode(polc)
|
||||
if got != tt.expectedBool {
|
||||
t.Fatalf("expected %v got %v for %v policy value", tt.expectedBool, got, tt.exitNodeIDPolicyValue)
|
||||
}
|
||||
@ -3913,11 +3925,13 @@ func TestFillAllowedSuggestions(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
policyStore := source.NewTestStoreOf(t, source.TestSettingOf(
|
||||
syspolicy.AllowedSuggestedExitNodes, tt.allowPolicy,
|
||||
pkey.AllowedSuggestedExitNodes, tt.allowPolicy,
|
||||
))
|
||||
syspolicy.MustRegisterStoreForTest(t, "TestStore", setting.DeviceScope, policyStore)
|
||||
|
||||
got := fillAllowedSuggestions()
|
||||
var polc policyclient.Client = nil // XXX TODO
|
||||
|
||||
got := fillAllowedSuggestions(polc)
|
||||
if got == nil {
|
||||
if tt.want == nil {
|
||||
return
|
||||
@ -4711,23 +4725,23 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "ShieldsUp/True",
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableIncomingConnections, "never")},
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableIncomingConnections, "never")},
|
||||
want: wantPrefsChanges(fieldChange{"ShieldsUp", true}),
|
||||
},
|
||||
{
|
||||
name: "ShieldsUp/False",
|
||||
initialPrefs: &ipn.Prefs{ShieldsUp: true},
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableIncomingConnections, "always")},
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableIncomingConnections, "always")},
|
||||
want: wantPrefsChanges(fieldChange{"ShieldsUp", false}),
|
||||
},
|
||||
{
|
||||
name: "ExitNodeID",
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.ExitNodeID, "foo")},
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.ExitNodeID, "foo")},
|
||||
want: wantPrefsChanges(fieldChange{"ExitNodeID", tailcfg.StableNodeID("foo")}),
|
||||
},
|
||||
{
|
||||
name: "EnableRunExitNode",
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(syspolicy.EnableRunExitNode, "always")},
|
||||
stringSettings: []source.TestSetting[string]{source.TestSettingOf(pkey.EnableRunExitNode, "always")},
|
||||
want: wantPrefsChanges(fieldChange{"AdvertiseRoutes", []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}}),
|
||||
},
|
||||
{
|
||||
@ -4736,9 +4750,9 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) {
|
||||
ExitNodeAllowLANAccess: true,
|
||||
},
|
||||
stringSettings: []source.TestSetting[string]{
|
||||
source.TestSettingOf(syspolicy.EnableServerMode, "always"),
|
||||
source.TestSettingOf(syspolicy.ExitNodeAllowLANAccess, "never"),
|
||||
source.TestSettingOf(syspolicy.ExitNodeIP, "127.0.0.1"),
|
||||
source.TestSettingOf(pkey.EnableServerMode, "always"),
|
||||
source.TestSettingOf(pkey.ExitNodeAllowLANAccess, "never"),
|
||||
source.TestSettingOf(pkey.ExitNodeIP, "127.0.0.1"),
|
||||
},
|
||||
want: wantPrefsChanges(
|
||||
fieldChange{"ForceDaemon", true},
|
||||
@ -4754,9 +4768,9 @@ func TestUpdatePrefsOnSysPolicyChange(t *testing.T) {
|
||||
AdvertiseRoutes: []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()},
|
||||
},
|
||||
stringSettings: []source.TestSetting[string]{
|
||||
source.TestSettingOf(syspolicy.EnableTailscaleDNS, "always"),
|
||||
source.TestSettingOf(syspolicy.ExitNodeID, "foo"),
|
||||
source.TestSettingOf(syspolicy.EnableRunExitNode, "always"),
|
||||
source.TestSettingOf(pkey.EnableTailscaleDNS, "always"),
|
||||
source.TestSettingOf(pkey.ExitNodeID, "foo"),
|
||||
source.TestSettingOf(pkey.EnableRunExitNode, "always"),
|
||||
},
|
||||
want: nil, // syspolicy settings match the preferences; no change notification is expected.
|
||||
},
|
||||
@ -4942,3 +4956,11 @@ func TestUpdateIngressLocked(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
BadDeps: map[string]string{
|
||||
"tailscale.com/util/syspolicy": "should only depend on syspolicy/policyclient + pkeys",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
@ -62,8 +62,6 @@ import (
|
||||
"tailscale.com/util/osdiag"
|
||||
"tailscale.com/util/progresstracking"
|
||||
"tailscale.com/util/rands"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
)
|
||||
@ -78,7 +76,6 @@ var handler = map[string]localAPIHandler{
|
||||
"cert/": (*Handler).serveCert,
|
||||
"file-put/": (*Handler).serveFilePut,
|
||||
"files/": (*Handler).serveFiles,
|
||||
"policy/": (*Handler).servePolicy,
|
||||
"profiles/": (*Handler).serveProfiles,
|
||||
|
||||
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
|
||||
@ -1389,53 +1386,6 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
|
||||
e.Encode(prefs)
|
||||
}
|
||||
|
||||
func (h *Handler) servePolicy(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "policy access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
suffix, ok := strings.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/policy/")
|
||||
if !ok {
|
||||
http.Error(w, "misconfigured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var scope setting.PolicyScope
|
||||
if suffix == "" {
|
||||
scope = setting.DefaultScope()
|
||||
} else if err := scope.UnmarshalText([]byte(suffix)); err != nil {
|
||||
http.Error(w, fmt.Sprintf("%q is not a valid scope", suffix), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := rsop.PolicyFor(scope)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var effectivePolicy *setting.Snapshot
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
effectivePolicy = policy.Get()
|
||||
case "POST":
|
||||
effectivePolicy, err = policy.Reload()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(effectivePolicy)
|
||||
}
|
||||
|
||||
type resJSON struct {
|
||||
Error string `json:",omitempty"`
|
||||
}
|
||||
|
67
ipn/localapi/syspolicy_api.go
Normal file
67
ipn/localapi/syspolicy_api.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
handler["policy/"] = (*Handler).servePolicy
|
||||
}
|
||||
|
||||
func (h *Handler) servePolicy(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "policy access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
suffix, ok := strings.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/policy/")
|
||||
if !ok {
|
||||
http.Error(w, "misconfigured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var scope setting.PolicyScope
|
||||
if suffix == "" {
|
||||
scope = setting.DefaultScope()
|
||||
} else if err := scope.UnmarshalText([]byte(suffix)); err != nil {
|
||||
http.Error(w, fmt.Sprintf("%q is not a valid scope", suffix), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := rsop.PolicyFor(scope)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var effectivePolicy *setting.Snapshot
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
effectivePolicy = policy.Get()
|
||||
case "POST":
|
||||
effectivePolicy, err = policy.Reload()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
e.Encode(effectivePolicy)
|
||||
}
|
17
ipn/prefs.go
17
ipn/prefs.go
@ -28,7 +28,8 @@ import (
|
||||
"tailscale.com/types/preftype"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// DefaultControlURL is the URL base of the control plane
|
||||
@ -688,16 +689,16 @@ func NewPrefs() *Prefs {
|
||||
//
|
||||
// If not configured, or if the configured value is a legacy name equivalent to
|
||||
// the default, then DefaultControlURL is returned instead.
|
||||
func (p PrefsView) ControlURLOrDefault() string {
|
||||
return p.ж.ControlURLOrDefault()
|
||||
func (p PrefsView) ControlURLOrDefault(polc policyclient.Client) string {
|
||||
return p.ж.ControlURLOrDefault(polc)
|
||||
}
|
||||
|
||||
// ControlURLOrDefault returns the coordination server's URL base.
|
||||
//
|
||||
// If not configured, or if the configured value is a legacy name equivalent to
|
||||
// the default, then DefaultControlURL is returned instead.
|
||||
func (p *Prefs) ControlURLOrDefault() string {
|
||||
controlURL, err := syspolicy.GetString(syspolicy.ControlURL, p.ControlURL)
|
||||
func (p *Prefs) ControlURLOrDefault(polc policyclient.Client) string {
|
||||
controlURL, err := polc.GetString(pkey.ControlURL, p.ControlURL)
|
||||
if err != nil {
|
||||
controlURL = p.ControlURL
|
||||
}
|
||||
@ -712,11 +713,11 @@ func (p *Prefs) ControlURLOrDefault() string {
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (p *Prefs) AdminPageURL() string {
|
||||
url := p.ControlURLOrDefault()
|
||||
func (p *Prefs) AdminPageURL(polc policyclient.Client) string {
|
||||
url := p.ControlURLOrDefault(polc)
|
||||
if IsLoginServerSynonym(url) {
|
||||
// TODO(crawshaw): In future release, make this https://console.tailscale.com
|
||||
url = "https://login.tailscale.com"
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/preftype"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func fieldsOf(t reflect.Type) (fields []string) {
|
||||
@ -1013,15 +1014,16 @@ func TestExitNodeIPOfArg(t *testing.T) {
|
||||
|
||||
func TestControlURLOrDefault(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,8 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/util/racebuild"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
@ -65,12 +66,14 @@ var getLogTargetOnce struct {
|
||||
func getLogTarget() string {
|
||||
getLogTargetOnce.Do(func() {
|
||||
envTarget, _ := os.LookupEnv("TS_LOG_TARGET")
|
||||
getLogTargetOnce.v, _ = syspolicy.GetString(syspolicy.LogTarget, envTarget)
|
||||
getLogTargetOnce.v, _ = SysPolicy.GetString(pkey.LogTarget, envTarget)
|
||||
})
|
||||
|
||||
return getLogTargetOnce.v
|
||||
}
|
||||
|
||||
var SysPolicy policyclient.Client = policyclient.NoPolicyClient{}
|
||||
|
||||
// LogURL is the base URL for the configured logtail server, or the default.
|
||||
// It is guaranteed to not terminate with any forward slashes.
|
||||
func LogURL() string {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/tstest/deptest"
|
||||
)
|
||||
|
||||
func TestLogHost(t *testing.T) {
|
||||
@ -36,3 +37,11 @@ func TestLogHost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
BadDeps: map[string]string{
|
||||
"tailscale.com/util/syspolicy": "should only depend on syspolicy/policyclient",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
@ -7,14 +7,15 @@ import (
|
||||
"fmt"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// GetSerialNumbers returns the serial number of the iOS/tvOS device as reported by an
|
||||
// MDM solution. It requires configuration via the DeviceSerialNumber system policy.
|
||||
// This is the only way to gather serial numbers on iOS and tvOS.
|
||||
func GetSerialNumbers(_ logger.Logf) ([]string, error) {
|
||||
s, err := syspolicy.GetString(syspolicy.DeviceSerialNumber, "")
|
||||
func GetSerialNumbers(polc policyclient.Client, _ logger.Logf) ([]string, error) {
|
||||
s, err := polc.GetString(pkey.DeviceSerialNumber, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get serial number from MDM: %v", err)
|
||||
}
|
||||
|
@ -59,10 +59,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// GetSerialNumber returns the platform serial sumber as reported by IOKit.
|
||||
func GetSerialNumbers(_ logger.Logf) ([]string, error) {
|
||||
func GetSerialNumbers(polc policyclient.Client, _ logger.Logf) ([]string, error) {
|
||||
csn := C.getSerialNumber()
|
||||
serialNumber := C.GoString(csn)
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/cibuild"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func TestGetSerialNumberMac(t *testing.T) {
|
||||
@ -20,7 +21,7 @@ func TestGetSerialNumberMac(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
sns, err := GetSerialNumbers(logger.Discard)
|
||||
sns, err := GetSerialNumbers(policyclient.NoPolicyClient{}, logger.Discard)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get serial number: %s", err)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/digitalocean/go-smbios/smbios"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// getByteFromSmbiosStructure retrieves a 8-bit unsigned integer at the given specOffset.
|
||||
@ -71,7 +72,7 @@ func init() {
|
||||
numOfTables = len(validTables)
|
||||
}
|
||||
|
||||
func GetSerialNumbers(logf logger.Logf) ([]string, error) {
|
||||
func GetSerialNumbers(polc policyclient.Client, logf logger.Logf) ([]string, error) {
|
||||
// Find SMBIOS data in operating system-specific location.
|
||||
rc, _, err := smbios.Stream()
|
||||
if err != nil {
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func TestGetSerialNumberNotMac(t *testing.T) {
|
||||
@ -21,7 +22,7 @@ func TestGetSerialNumberNotMac(t *testing.T) {
|
||||
// Comment out skip for local testing.
|
||||
t.Skip()
|
||||
|
||||
sns, err := GetSerialNumbers(logger.Discard)
|
||||
sns, err := GetSerialNumbers(policyclient.NoPolicyClient{}, logger.Discard)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get serial number: %s", err)
|
||||
}
|
||||
|
@ -15,9 +15,10 @@ import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
// GetSerialNumber returns client machine serial number(s).
|
||||
func GetSerialNumbers(_ logger.Logf) ([]string, error) {
|
||||
func GetSerialNumbers(polc policyclient.Client, _ logger.Logf) ([]string, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
@ -7,10 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
)
|
||||
|
||||
func TestGetSerialNumber(t *testing.T) {
|
||||
// ensure GetSerialNumbers is implemented
|
||||
// or covered by a stub on a given platform.
|
||||
_, _ = GetSerialNumbers(logger.Discard)
|
||||
_, _ = GetSerialNumbers(policyclient.NoPolicyClient{}, logger.Discard)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/proxymap"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
@ -52,6 +53,7 @@ type System struct {
|
||||
Netstack SubSystem[NetstackImpl] // actually a *netstack.Impl
|
||||
DriveForLocal SubSystem[drive.FileSystemForLocal]
|
||||
DriveForRemote SubSystem[drive.FileSystemForRemote]
|
||||
PolicyClient SubSystem[policyclient.Client]
|
||||
|
||||
// InitialConfig is initial server config, if any.
|
||||
// It is nil if the node is not in declarative mode.
|
||||
@ -149,6 +151,13 @@ func (s *System) UserMetricsRegistry() *usermetric.Registry {
|
||||
return &s.userMetricsRegistry
|
||||
}
|
||||
|
||||
func (s *System) PolicyClientOrDefault() policyclient.Client {
|
||||
if v, ok := s.PolicyClient.GetOK(); ok {
|
||||
return v
|
||||
}
|
||||
return policyclient.NoPolicyClient{}
|
||||
}
|
||||
|
||||
// SubSystem represents some subsystem of the Tailscale node daemon.
|
||||
//
|
||||
// A subsystem can be set to a value, and then later retrieved. A subsystem
|
||||
|
@ -49,6 +49,9 @@ import (
|
||||
_ "tailscale.com/util/clientmetric"
|
||||
_ "tailscale.com/util/multierr"
|
||||
_ "tailscale.com/util/osshare"
|
||||
_ "tailscale.com/util/syspolicy"
|
||||
_ "tailscale.com/util/syspolicy/pkey"
|
||||
_ "tailscale.com/util/syspolicy/policyclient"
|
||||
_ "tailscale.com/version"
|
||||
_ "tailscale.com/version/distro"
|
||||
_ "tailscale.com/wgengine"
|
||||
|
@ -49,6 +49,9 @@ import (
|
||||
_ "tailscale.com/util/clientmetric"
|
||||
_ "tailscale.com/util/multierr"
|
||||
_ "tailscale.com/util/osshare"
|
||||
_ "tailscale.com/util/syspolicy"
|
||||
_ "tailscale.com/util/syspolicy/pkey"
|
||||
_ "tailscale.com/util/syspolicy/policyclient"
|
||||
_ "tailscale.com/version"
|
||||
_ "tailscale.com/version/distro"
|
||||
_ "tailscale.com/wgengine"
|
||||
|
@ -49,6 +49,9 @@ import (
|
||||
_ "tailscale.com/util/clientmetric"
|
||||
_ "tailscale.com/util/multierr"
|
||||
_ "tailscale.com/util/osshare"
|
||||
_ "tailscale.com/util/syspolicy"
|
||||
_ "tailscale.com/util/syspolicy/pkey"
|
||||
_ "tailscale.com/util/syspolicy/policyclient"
|
||||
_ "tailscale.com/version"
|
||||
_ "tailscale.com/version/distro"
|
||||
_ "tailscale.com/wgengine"
|
||||
|
@ -49,6 +49,9 @@ import (
|
||||
_ "tailscale.com/util/clientmetric"
|
||||
_ "tailscale.com/util/multierr"
|
||||
_ "tailscale.com/util/osshare"
|
||||
_ "tailscale.com/util/syspolicy"
|
||||
_ "tailscale.com/util/syspolicy/pkey"
|
||||
_ "tailscale.com/util/syspolicy/policyclient"
|
||||
_ "tailscale.com/version"
|
||||
_ "tailscale.com/version/distro"
|
||||
_ "tailscale.com/wgengine"
|
||||
|
@ -58,6 +58,8 @@ import (
|
||||
_ "tailscale.com/util/osdiag"
|
||||
_ "tailscale.com/util/osshare"
|
||||
_ "tailscale.com/util/syspolicy"
|
||||
_ "tailscale.com/util/syspolicy/pkey"
|
||||
_ "tailscale.com/util/syspolicy/policyclient"
|
||||
_ "tailscale.com/util/winutil"
|
||||
_ "tailscale.com/version"
|
||||
_ "tailscale.com/version/distro"
|
||||
|
@ -24,6 +24,7 @@ func TestDeps(t *testing.T) {
|
||||
"github.com/google/uuid": "see tailscale/tailscale#13760",
|
||||
"tailscale.com/clientupdate/distsign": "downloads via AppStore, not distsign",
|
||||
"github.com/tailscale/hujson": "no config file support on iOS",
|
||||
"tailscale.com/util/syspolicy": "should only depend on syspolicy/policyclient",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ package syspolicy
|
||||
|
||||
import (
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
@ -89,22 +90,22 @@ func (s handlerStore) RegisterChangeCallback(callback func()) (unregister func()
|
||||
}
|
||||
|
||||
// ReadString implements [source.Store].
|
||||
func (s handlerStore) ReadString(key setting.Key) (string, error) {
|
||||
func (s handlerStore) ReadString(key pkey.Key) (string, error) {
|
||||
return s.h.ReadString(string(key))
|
||||
}
|
||||
|
||||
// ReadUInt64 implements [source.Store].
|
||||
func (s handlerStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
func (s handlerStore) ReadUInt64(key pkey.Key) (uint64, error) {
|
||||
return s.h.ReadUInt64(string(key))
|
||||
}
|
||||
|
||||
// ReadBoolean implements [source.Store].
|
||||
func (s handlerStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
func (s handlerStore) ReadBoolean(key pkey.Key) (bool, error) {
|
||||
return s.h.ReadBoolean(string(key))
|
||||
}
|
||||
|
||||
// ReadStringArray implements [source.Store].
|
||||
func (s handlerStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
func (s handlerStore) ReadStringArray(key pkey.Key) ([]string, error) {
|
||||
return s.h.ReadStringArray(string(key))
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ type TB interface {
|
||||
Errorf(format string, args ...any)
|
||||
Fatal(args ...any)
|
||||
Fatalf(format string, args ...any)
|
||||
Skip(...any)
|
||||
}
|
||||
|
||||
// EqualJSONForTest compares the JSON in j1 and j2 for semantic equality.
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"tailscale.com/util/slicesx"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
@ -209,7 +210,7 @@ func scopeMetrics(origin *setting.Origin) *policyScopeMetrics {
|
||||
|
||||
var (
|
||||
settingMetricsMu sync.RWMutex
|
||||
settingMetricsMap map[setting.Key]*settingMetrics
|
||||
settingMetricsMap map[pkey.Key]*settingMetrics
|
||||
)
|
||||
|
||||
func settingMetricsFor(setting *setting.Definition) *settingMetrics {
|
||||
@ -283,8 +284,8 @@ func SetHooksForTest(tb internal.TB, addMetric, setMetric metricFn) {
|
||||
lazyUserMetrics.SetForTest(tb, newScopeMetrics(setting.UserSetting), nil)
|
||||
}
|
||||
|
||||
func newSettingMetric(key setting.Key, scope setting.Scope, suffix string, typ clientmetric.Type) metric {
|
||||
name := strings.ReplaceAll(string(key), string(setting.KeyPathSeparator), "_")
|
||||
func newSettingMetric(key pkey.Key, scope setting.Scope, suffix string, typ clientmetric.Type) metric {
|
||||
name := strings.ReplaceAll(string(key), string(pkey.KeyPathSeparator), "_")
|
||||
return newMetric([]string{name, metricScopeName(scope), suffix}, typ)
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,14 @@ import (
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
func TestSettingMetricNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key setting.Key
|
||||
key pkey.Key
|
||||
scope setting.Scope
|
||||
suffix string
|
||||
typ clientmetric.Type
|
||||
|
133
util/syspolicy/pkey/pkey.go
Normal file
133
util/syspolicy/pkey/pkey.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package pkey defines the keys used to store system policies in the registry.
|
||||
//
|
||||
// This is a leaf package meant to only contain string constants, not code.
|
||||
package pkey
|
||||
|
||||
// Key is a string that uniquely identifies a policy and must remain unchanged
|
||||
// once established and documented for a given policy setting. It may contain
|
||||
// alphanumeric characters and zero or more [KeyPathSeparator]s to group
|
||||
// individual policy settings into categories.
|
||||
type Key string
|
||||
|
||||
// KeyPathSeparator allows logical grouping of policy settings into categories.
|
||||
const KeyPathSeparator = '/'
|
||||
|
||||
// The const block below lists known policy keys.
|
||||
// When adding a key to this list, remember to add a corresponding
|
||||
// [setting.Definition] to syspolicy/policy_keys.go's [implicitDefinitions].
|
||||
// Otherwise, the TestKnownKeysRegistered test will fail as a reminder.
|
||||
|
||||
const (
|
||||
// Keys with a string value
|
||||
ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
|
||||
LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
|
||||
Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
|
||||
// ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
|
||||
// Exit node ID takes precedence over exit node IP.
|
||||
// To find the node ID, go to /api.md#device.
|
||||
ExitNodeID Key = "ExitNodeID"
|
||||
ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
|
||||
|
||||
// Keys with a string value that specifies an option: "always", "never", "user-decides".
|
||||
// The default is "user-decides" unless otherwise stated. Enforcement of
|
||||
// these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
|
||||
// typically hide menu items related to policies that are enforced.
|
||||
EnableIncomingConnections Key = "AllowIncomingConnections"
|
||||
EnableServerMode Key = "UnattendedMode"
|
||||
ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
|
||||
EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
|
||||
EnableTailscaleSubnets Key = "UseTailscaleSubnets"
|
||||
// CheckUpdates is the key to signal if the updater should periodically
|
||||
// check for updates.
|
||||
CheckUpdates Key = "CheckUpdates"
|
||||
// ApplyUpdates is the key to signal if updates should be automatically
|
||||
// installed. Its value is "InstallUpdates" because of an awkwardly-named
|
||||
// visibility option "ApplyUpdates" on MacOS.
|
||||
ApplyUpdates Key = "InstallUpdates"
|
||||
// EnableRunExitNode controls if the device acts as an exit node. Even when
|
||||
// running as an exit node, the device must be approved by a tailnet
|
||||
// administrator. Its name is slightly awkward because RunExitNodeVisibility
|
||||
// predates this option but is preserved for backwards compatibility.
|
||||
EnableRunExitNode Key = "AdvertiseExitNode"
|
||||
|
||||
// Keys with a string value that controls visibility: "show", "hide".
|
||||
// The default is "show" unless otherwise stated. Enforcement of these
|
||||
// policies is typically performed by the UI code for the relevant operating
|
||||
// system.
|
||||
AdminConsoleVisibility Key = "AdminConsole"
|
||||
NetworkDevicesVisibility Key = "NetworkDevices"
|
||||
TestMenuVisibility Key = "TestMenu"
|
||||
UpdateMenuVisibility Key = "UpdateMenu"
|
||||
ResetToDefaultsVisibility Key = "ResetToDefaults"
|
||||
// RunExitNodeVisibility controls if the "run as exit node" menu item is
|
||||
// visible, without controlling the setting itself. This is preserved for
|
||||
// backwards compatibility but prefer EnableRunExitNode in new deployments.
|
||||
RunExitNodeVisibility Key = "RunExitNode"
|
||||
PreferencesMenuVisibility Key = "PreferencesMenu"
|
||||
ExitNodeMenuVisibility Key = "ExitNodesPicker"
|
||||
// AutoUpdateVisibility is the key to signal if the menu item for automatic
|
||||
// installation of updates should be visible. It is only used by macsys
|
||||
// installations and uses the Sparkle naming convention, even though it does
|
||||
// not actually control updates, merely the UI for that setting.
|
||||
AutoUpdateVisibility Key = "ApplyUpdates"
|
||||
// SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
|
||||
// When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
|
||||
SuggestedExitNodeVisibility Key = "SuggestedExitNode"
|
||||
// OnboardingFlowVisibility controls the visibility of the onboarding flow in the client GUI.
|
||||
// When this system policy is set to 'hide', the onboarding flow is never shown to the user.
|
||||
OnboardingFlowVisibility Key = "OnboardingFlow"
|
||||
|
||||
// Keys with a string value formatted for use with time.ParseDuration().
|
||||
KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
|
||||
|
||||
// Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
|
||||
// DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
|
||||
// The default is 0 unless otherwise stated.
|
||||
LogSCMInteractions Key = "LogSCMInteractions"
|
||||
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
|
||||
|
||||
// PostureChecking indicates if posture checking is enabled and the client shall gather
|
||||
// posture data.
|
||||
// Key is a string value that specifies an option: "always", "never", "user-decides".
|
||||
// The default is "user-decides" unless otherwise stated.
|
||||
PostureChecking Key = "PostureChecking"
|
||||
// DeviceSerialNumber is the serial number of the device that is running Tailscale.
|
||||
// This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
|
||||
// We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
|
||||
DeviceSerialNumber Key = "DeviceSerialNumber"
|
||||
|
||||
// ManagedByOrganizationName indicates the name of the organization managing the Tailscale
|
||||
// install. It is displayed inside the client UI in a prominent location.
|
||||
ManagedByOrganizationName Key = "ManagedByOrganizationName"
|
||||
// ManagedByCaption is an info message displayed inside the client UI as a caption when
|
||||
// ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
|
||||
// for Tailscale within the organization.
|
||||
ManagedByCaption Key = "ManagedByCaption"
|
||||
// ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
|
||||
// organization. A button in the client UI provides easy access to this URL.
|
||||
ManagedByURL Key = "ManagedByURL"
|
||||
|
||||
// AuthKey is an auth key that will be used to login whenever the backend starts. This can be used to
|
||||
// automatically authenticate managed devices, without requiring user interaction.
|
||||
AuthKey Key = "AuthKey"
|
||||
|
||||
// MachineCertificateSubject is the exact name of a Subject that needs
|
||||
// to be present in an identity's certificate chain to sign a RegisterRequest,
|
||||
// formatted as per pkix.Name.String(). The Subject may be that of the identity
|
||||
// itself, an intermediate CA or the root CA.
|
||||
//
|
||||
// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
|
||||
MachineCertificateSubject Key = "MachineCertificateSubject"
|
||||
|
||||
// Hostname is the hostname of the device that is running Tailscale.
|
||||
// When this policy is set, it overrides the hostname that the client
|
||||
// would otherwise obtain from the OS, e.g. by calling os.Hostname().
|
||||
Hostname Key = "Hostname"
|
||||
|
||||
// Keys with a string array value.
|
||||
// AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
|
||||
AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
|
||||
)
|
@ -6,176 +6,54 @@ package syspolicy
|
||||
import (
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
|
||||
// Key is a string that uniquely identifies a policy and must remain unchanged
|
||||
// once established and documented for a given policy setting. It may contain
|
||||
// alphanumeric characters and zero or more [KeyPathSeparator]s to group
|
||||
// individual policy settings into categories.
|
||||
type Key = setting.Key
|
||||
|
||||
// The const block below lists known policy keys.
|
||||
// When adding a key to this list, remember to add a corresponding
|
||||
// [setting.Definition] to [implicitDefinitions] below.
|
||||
// Otherwise, the [TestKnownKeysRegistered] test will fail as a reminder.
|
||||
|
||||
const (
|
||||
// Keys with a string value
|
||||
ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
|
||||
LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
|
||||
Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
|
||||
// ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
|
||||
// Exit node ID takes precedence over exit node IP.
|
||||
// To find the node ID, go to /api.md#device.
|
||||
ExitNodeID Key = "ExitNodeID"
|
||||
ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
|
||||
|
||||
// Keys with a string value that specifies an option: "always", "never", "user-decides".
|
||||
// The default is "user-decides" unless otherwise stated. Enforcement of
|
||||
// these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
|
||||
// typically hide menu items related to policies that are enforced.
|
||||
EnableIncomingConnections Key = "AllowIncomingConnections"
|
||||
EnableServerMode Key = "UnattendedMode"
|
||||
ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
|
||||
EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
|
||||
EnableTailscaleSubnets Key = "UseTailscaleSubnets"
|
||||
// CheckUpdates is the key to signal if the updater should periodically
|
||||
// check for updates.
|
||||
CheckUpdates Key = "CheckUpdates"
|
||||
// ApplyUpdates is the key to signal if updates should be automatically
|
||||
// installed. Its value is "InstallUpdates" because of an awkwardly-named
|
||||
// visibility option "ApplyUpdates" on MacOS.
|
||||
ApplyUpdates Key = "InstallUpdates"
|
||||
// EnableRunExitNode controls if the device acts as an exit node. Even when
|
||||
// running as an exit node, the device must be approved by a tailnet
|
||||
// administrator. Its name is slightly awkward because RunExitNodeVisibility
|
||||
// predates this option but is preserved for backwards compatibility.
|
||||
EnableRunExitNode Key = "AdvertiseExitNode"
|
||||
|
||||
// Keys with a string value that controls visibility: "show", "hide".
|
||||
// The default is "show" unless otherwise stated. Enforcement of these
|
||||
// policies is typically performed by the UI code for the relevant operating
|
||||
// system.
|
||||
AdminConsoleVisibility Key = "AdminConsole"
|
||||
NetworkDevicesVisibility Key = "NetworkDevices"
|
||||
TestMenuVisibility Key = "TestMenu"
|
||||
UpdateMenuVisibility Key = "UpdateMenu"
|
||||
ResetToDefaultsVisibility Key = "ResetToDefaults"
|
||||
// RunExitNodeVisibility controls if the "run as exit node" menu item is
|
||||
// visible, without controlling the setting itself. This is preserved for
|
||||
// backwards compatibility but prefer EnableRunExitNode in new deployments.
|
||||
RunExitNodeVisibility Key = "RunExitNode"
|
||||
PreferencesMenuVisibility Key = "PreferencesMenu"
|
||||
ExitNodeMenuVisibility Key = "ExitNodesPicker"
|
||||
// AutoUpdateVisibility is the key to signal if the menu item for automatic
|
||||
// installation of updates should be visible. It is only used by macsys
|
||||
// installations and uses the Sparkle naming convention, even though it does
|
||||
// not actually control updates, merely the UI for that setting.
|
||||
AutoUpdateVisibility Key = "ApplyUpdates"
|
||||
// SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
|
||||
// When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
|
||||
SuggestedExitNodeVisibility Key = "SuggestedExitNode"
|
||||
// OnboardingFlowVisibility controls the visibility of the onboarding flow in the client GUI.
|
||||
// When this system policy is set to 'hide', the onboarding flow is never shown to the user.
|
||||
OnboardingFlowVisibility Key = "OnboardingFlow"
|
||||
|
||||
// Keys with a string value formatted for use with time.ParseDuration().
|
||||
KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
|
||||
|
||||
// Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
|
||||
// DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
|
||||
// The default is 0 unless otherwise stated.
|
||||
LogSCMInteractions Key = "LogSCMInteractions"
|
||||
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
|
||||
|
||||
// PostureChecking indicates if posture checking is enabled and the client shall gather
|
||||
// posture data.
|
||||
// Key is a string value that specifies an option: "always", "never", "user-decides".
|
||||
// The default is "user-decides" unless otherwise stated.
|
||||
PostureChecking Key = "PostureChecking"
|
||||
// DeviceSerialNumber is the serial number of the device that is running Tailscale.
|
||||
// This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
|
||||
// We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
|
||||
DeviceSerialNumber Key = "DeviceSerialNumber"
|
||||
|
||||
// ManagedByOrganizationName indicates the name of the organization managing the Tailscale
|
||||
// install. It is displayed inside the client UI in a prominent location.
|
||||
ManagedByOrganizationName Key = "ManagedByOrganizationName"
|
||||
// ManagedByCaption is an info message displayed inside the client UI as a caption when
|
||||
// ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
|
||||
// for Tailscale within the organization.
|
||||
ManagedByCaption Key = "ManagedByCaption"
|
||||
// ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
|
||||
// organization. A button in the client UI provides easy access to this URL.
|
||||
ManagedByURL Key = "ManagedByURL"
|
||||
|
||||
// AuthKey is an auth key that will be used to login whenever the backend starts. This can be used to
|
||||
// automatically authenticate managed devices, without requiring user interaction.
|
||||
AuthKey Key = "AuthKey"
|
||||
|
||||
// MachineCertificateSubject is the exact name of a Subject that needs
|
||||
// to be present in an identity's certificate chain to sign a RegisterRequest,
|
||||
// formatted as per pkix.Name.String(). The Subject may be that of the identity
|
||||
// itself, an intermediate CA or the root CA.
|
||||
//
|
||||
// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
|
||||
MachineCertificateSubject Key = "MachineCertificateSubject"
|
||||
|
||||
// Hostname is the hostname of the device that is running Tailscale.
|
||||
// When this policy is set, it overrides the hostname that the client
|
||||
// would otherwise obtain from the OS, e.g. by calling os.Hostname().
|
||||
Hostname Key = "Hostname"
|
||||
|
||||
// Keys with a string array value.
|
||||
// AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
|
||||
AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
|
||||
)
|
||||
|
||||
// implicitDefinitions is a list of [setting.Definition] that will be registered
|
||||
// automatically when the policy setting definitions are first used by the syspolicy package hierarchy.
|
||||
// This includes the first time a policy needs to be read from any source.
|
||||
var implicitDefinitions = []*setting.Definition{
|
||||
// Device policy settings (can only be configured on a per-device basis):
|
||||
setting.NewDefinition(AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue),
|
||||
setting.NewDefinition(ApplyUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(AuthKey, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(CheckUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(ControlURL, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(DeviceSerialNumber, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(EnableIncomingConnections, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(EnableRunExitNode, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(EnableServerMode, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(EnableTailscaleDNS, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(EnableTailscaleSubnets, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(ExitNodeAllowLANAccess, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(ExitNodeID, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(ExitNodeIP, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(FlushDNSOnSessionUnlock, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(Hostname, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(LogSCMInteractions, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(LogTarget, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(MachineCertificateSubject, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(PostureChecking, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(Tailnet, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue),
|
||||
setting.NewDefinition(pkey.ApplyUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.AuthKey, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.CheckUpdates, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.ControlURL, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.DeviceSerialNumber, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.EnableIncomingConnections, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.EnableRunExitNode, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.EnableServerMode, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.EnableTailscaleDNS, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.EnableTailscaleSubnets, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.ExitNodeAllowLANAccess, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.ExitNodeID, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.ExitNodeIP, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.FlushDNSOnSessionUnlock, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(pkey.Hostname, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.LogSCMInteractions, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(pkey.LogTarget, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.MachineCertificateSubject, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.PostureChecking, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
setting.NewDefinition(pkey.Tailnet, setting.DeviceSetting, setting.StringValue),
|
||||
|
||||
// User policy settings (can be configured on a user- or device-basis):
|
||||
setting.NewDefinition(AdminConsoleVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(AutoUpdateVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(ExitNodeMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(KeyExpirationNoticeTime, setting.UserSetting, setting.DurationValue),
|
||||
setting.NewDefinition(ManagedByCaption, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(ManagedByOrganizationName, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(ManagedByURL, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(NetworkDevicesVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(PreferencesMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(ResetToDefaultsVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(RunExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(SuggestedExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(TestMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(UpdateMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(OnboardingFlowVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.AdminConsoleVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.AutoUpdateVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.ExitNodeMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.KeyExpirationNoticeTime, setting.UserSetting, setting.DurationValue),
|
||||
setting.NewDefinition(pkey.ManagedByCaption, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.ManagedByOrganizationName, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.ManagedByURL, setting.UserSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.NetworkDevicesVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.PreferencesMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.ResetToDefaultsVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.RunExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.SuggestedExitNodeVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.TestMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.UpdateMenuVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
setting.NewDefinition(pkey.OnboardingFlowVisibility, setting.UserSetting, setting.VisibilityValue),
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -199,7 +77,7 @@ var implicitDefinitionMap lazy.SyncValue[setting.DefinitionMap]
|
||||
// WellKnownSettingDefinition returns a well-known, implicit setting definition by its key,
|
||||
// or an [ErrNoSuchKey] if a policy setting with the specified key does not exist
|
||||
// among implicit policy definitions.
|
||||
func WellKnownSettingDefinition(k Key) (*setting.Definition, error) {
|
||||
func WellKnownSettingDefinition(k pkey.Key) (*setting.Definition, error) {
|
||||
m, err := implicitDefinitionMap.GetErr(func() (setting.DefinitionMap, error) {
|
||||
return setting.DefinitionMapOf(implicitDefinitions)
|
||||
})
|
||||
@ -215,6 +93,7 @@ func WellKnownSettingDefinition(k Key) (*setting.Definition, error) {
|
||||
// RegisterWellKnownSettingsForTest registers all implicit setting definitions
|
||||
// for the duration of the test.
|
||||
func RegisterWellKnownSettingsForTest(tb TB) {
|
||||
tb.Skip("XXX delete this func")
|
||||
tb.Helper()
|
||||
err := setting.SetDefinitionsForTest(tb, implicitDefinitions...)
|
||||
if err != nil {
|
||||
|
@ -14,9 +14,12 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
type Key = pkey.Key
|
||||
|
||||
func TestKnownKeysRegistered(t *testing.T) {
|
||||
keyConsts, err := listStringConsts[Key]("policy_keys.go")
|
||||
if err != nil {
|
||||
|
51
util/syspolicy/policyclient/policyclient.go
Normal file
51
util/syspolicy/policyclient/policyclient.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package policyclient contains the minimal syspolicy interface as needed by
|
||||
// client code using syspolicy without bringing in the entire syspolicy
|
||||
// universe.
|
||||
package policyclient
|
||||
|
||||
import "tailscale.com/util/syspolicy/pkey"
|
||||
|
||||
type Client interface {
|
||||
// GetString returns a string policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
GetString(key pkey.Key, defaultValue string) (string, error)
|
||||
|
||||
GetStringArray(key pkey.Key, defaultValue []string) ([]string, error)
|
||||
|
||||
GetBoolean(key pkey.Key, defaultValue bool) (bool, error)
|
||||
|
||||
SetDebugLoggingEnabled(enabled bool)
|
||||
|
||||
RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error)
|
||||
}
|
||||
|
||||
// NoPolicyClient is a no-op implementation of Client that only
|
||||
// returns default values.
|
||||
type NoPolicyClient struct{}
|
||||
|
||||
var _ Client = NoPolicyClient{}
|
||||
|
||||
func (NoPolicyClient) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (NoPolicyClient) GetString(key pkey.Key, defaultValue string) (string, error) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (NoPolicyClient) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (NoPolicyClient) SetDebugLoggingEnabled(enabled bool) {}
|
||||
|
||||
func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
type PolicyChange interface {
|
||||
HasChanged(key pkey.Key) bool
|
||||
}
|
@ -11,6 +11,8 @@ import (
|
||||
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -20,7 +22,7 @@ type Change[T any] struct {
|
||||
}
|
||||
|
||||
// PolicyChangeCallback is a function called whenever a policy changes.
|
||||
type PolicyChangeCallback func(*PolicyChange)
|
||||
type PolicyChangeCallback func(policyclient.PolicyChange)
|
||||
|
||||
// PolicyChange describes a policy change.
|
||||
type PolicyChange struct {
|
||||
@ -38,7 +40,7 @@ func (c PolicyChange) Old() *setting.Snapshot {
|
||||
}
|
||||
|
||||
// HasChanged reports whether a policy setting with the specified [setting.Key], has changed.
|
||||
func (c PolicyChange) HasChanged(key setting.Key) bool {
|
||||
func (c PolicyChange) HasChanged(key pkey.Key) bool {
|
||||
new, newErr := c.snapshots.New.GetErr(key)
|
||||
old, oldErr := c.snapshots.Old.GetErr(key)
|
||||
if newErr != nil && oldErr != nil {
|
||||
|
@ -15,6 +15,8 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
@ -80,7 +82,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
type sourceConfig struct {
|
||||
name string
|
||||
scope setting.PolicyScope
|
||||
settingKey setting.Key
|
||||
settingKey pkey.Key
|
||||
settingValue string
|
||||
wantEffective bool
|
||||
}
|
||||
@ -113,7 +115,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||||
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||||
},
|
||||
@ -129,7 +131,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||||
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||||
},
|
||||
@ -159,7 +161,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||||
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||||
"TestKeyC": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
|
||||
@ -191,7 +193,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
|
||||
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||||
}, setting.DeviceScope),
|
||||
@ -245,7 +247,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("TestValueF", nil, setting.NewNamedOrigin("TestSourceF", setting.DeviceScope)),
|
||||
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||||
"TestKeyC": setting.RawItemWith("TestValueE", nil, setting.NewNamedOrigin("TestSourceE", setting.DeviceScope)),
|
||||
@ -263,7 +265,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
}, setting.CurrentUserScope, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
},
|
||||
@ -288,7 +290,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
"TestKeyB": setting.RawItemWith("UserValue", nil, setting.NewNamedOrigin("TestSourceUser", setting.CurrentUserScope)),
|
||||
}, setting.CurrentUserScope),
|
||||
@ -321,7 +323,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: true,
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
"TestKeyB": setting.RawItemWith("ProfileValue", nil, setting.NewNamedOrigin("TestSourceProfile", setting.CurrentProfileScope)),
|
||||
}, setting.CurrentUserScope),
|
||||
@ -347,7 +349,7 @@ func TestRegisterSourceAndGetEffectivePolicy(t *testing.T) {
|
||||
wantEffective: false, // Registering a user source should have no impact on the device policy.
|
||||
},
|
||||
},
|
||||
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
wantSnapshot: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
}, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||||
},
|
||||
@ -497,61 +499,61 @@ func TestPolicyFor(t *testing.T) {
|
||||
func TestPolicyChangeHasChanged(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
old, new map[setting.Key]setting.RawItem
|
||||
wantChanged []setting.Key
|
||||
wantUnchanged []setting.Key
|
||||
old, new map[pkey.Key]setting.RawItem
|
||||
wantChanged []pkey.Key
|
||||
wantUnchanged []pkey.Key
|
||||
}{
|
||||
{
|
||||
name: "String-Settings",
|
||||
old: map[setting.Key]setting.RawItem{
|
||||
old: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf("Old"),
|
||||
"UnchangedSetting": setting.RawItemOf("Value"),
|
||||
},
|
||||
new: map[setting.Key]setting.RawItem{
|
||||
new: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf("New"),
|
||||
"UnchangedSetting": setting.RawItemOf("Value"),
|
||||
},
|
||||
wantChanged: []setting.Key{"ChangedSetting"},
|
||||
wantUnchanged: []setting.Key{"UnchangedSetting"},
|
||||
wantChanged: []pkey.Key{"ChangedSetting"},
|
||||
wantUnchanged: []pkey.Key{"UnchangedSetting"},
|
||||
},
|
||||
{
|
||||
name: "UInt64-Settings",
|
||||
old: map[setting.Key]setting.RawItem{
|
||||
old: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf(uint64(0)),
|
||||
"UnchangedSetting": setting.RawItemOf(uint64(42)),
|
||||
},
|
||||
new: map[setting.Key]setting.RawItem{
|
||||
new: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf(uint64(1)),
|
||||
"UnchangedSetting": setting.RawItemOf(uint64(42)),
|
||||
},
|
||||
wantChanged: []setting.Key{"ChangedSetting"},
|
||||
wantUnchanged: []setting.Key{"UnchangedSetting"},
|
||||
wantChanged: []pkey.Key{"ChangedSetting"},
|
||||
wantUnchanged: []pkey.Key{"UnchangedSetting"},
|
||||
},
|
||||
{
|
||||
name: "StringSlice-Settings",
|
||||
old: map[setting.Key]setting.RawItem{
|
||||
old: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf([]string{"Chicago"}),
|
||||
"UnchangedSetting": setting.RawItemOf([]string{"String1", "String2"}),
|
||||
},
|
||||
new: map[setting.Key]setting.RawItem{
|
||||
new: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf([]string{"New York"}),
|
||||
"UnchangedSetting": setting.RawItemOf([]string{"String1", "String2"}),
|
||||
},
|
||||
wantChanged: []setting.Key{"ChangedSetting"},
|
||||
wantUnchanged: []setting.Key{"UnchangedSetting"},
|
||||
wantChanged: []pkey.Key{"ChangedSetting"},
|
||||
wantUnchanged: []pkey.Key{"UnchangedSetting"},
|
||||
},
|
||||
{
|
||||
name: "Int8-Settings", // We don't have actual int8 settings, but this should still work.
|
||||
old: map[setting.Key]setting.RawItem{
|
||||
old: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf(int8(0)),
|
||||
"UnchangedSetting": setting.RawItemOf(int8(42)),
|
||||
},
|
||||
new: map[setting.Key]setting.RawItem{
|
||||
new: map[pkey.Key]setting.RawItem{
|
||||
"ChangedSetting": setting.RawItemOf(int8(1)),
|
||||
"UnchangedSetting": setting.RawItemOf(int8(42)),
|
||||
},
|
||||
wantChanged: []setting.Key{"ChangedSetting"},
|
||||
wantUnchanged: []setting.Key{"UnchangedSetting"},
|
||||
wantChanged: []pkey.Key{"ChangedSetting"},
|
||||
wantUnchanged: []pkey.Key{"UnchangedSetting"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -601,8 +603,8 @@ func TestChangePolicySetting(t *testing.T) {
|
||||
}
|
||||
|
||||
// Subscribe to the policy change callback...
|
||||
policyChanged := make(chan *PolicyChange)
|
||||
unregister := policy.RegisterChangeCallback(func(pc *PolicyChange) { policyChanged <- pc })
|
||||
policyChanged := make(chan policyclient.PolicyChange)
|
||||
unregister := policy.RegisterChangeCallback(func(pc policyclient.PolicyChange) { policyChanged <- pc })
|
||||
t.Cleanup(unregister)
|
||||
|
||||
// ...make the change, and measure the time between initiating the change
|
||||
@ -629,10 +631,10 @@ func TestChangePolicySetting(t *testing.T) {
|
||||
if change.HasChanged(settingB.Key()) {
|
||||
t.Errorf("Policy setting %q was unexpectedly changed", settingB.Key())
|
||||
}
|
||||
if _, ok := change.Old().GetSetting(settingA.Key()); ok {
|
||||
if _, ok := change.(*PolicyChange).Old().GetSetting(settingA.Key()); ok {
|
||||
t.Fatalf("Policy setting %q unexpectedly exists", settingA.Key())
|
||||
}
|
||||
if gotValue := change.New().Get(settingA.Key()); gotValue != wantValueA {
|
||||
if gotValue := change.(*PolicyChange).New().Get(settingA.Key()); gotValue != wantValueA {
|
||||
t.Errorf("Policy setting %q: got %q; want %q", settingA.Key(), gotValue, wantValueA)
|
||||
}
|
||||
|
||||
@ -682,10 +684,10 @@ drain:
|
||||
if change.HasChanged(settingA.Key()) {
|
||||
t.Errorf("Policy setting %q was unexpectedly changed", settingA.Key())
|
||||
}
|
||||
if _, ok := change.Old().GetSetting(settingB.Key()); ok {
|
||||
if _, ok := change.(*PolicyChange).Old().GetSetting(settingB.Key()); ok {
|
||||
t.Fatalf("Policy setting %q unexpectedly exists", settingB.Key())
|
||||
}
|
||||
if gotValue := change.New().Get(settingB.Key()); gotValue != wantValueB {
|
||||
if gotValue := change.(*PolicyChange).New().Get(settingB.Key()); gotValue != wantValueB {
|
||||
t.Errorf("Policy setting %q: got %q; want %q", settingB.Key(), gotValue, wantValueB)
|
||||
}
|
||||
|
||||
@ -852,8 +854,8 @@ func TestReplacePolicySource(t *testing.T) {
|
||||
}
|
||||
|
||||
// Subscribe to the policy change callback.
|
||||
policyChanged := make(chan *PolicyChange, 1)
|
||||
unregister := policy.RegisterChangeCallback(func(pc *PolicyChange) { policyChanged <- pc })
|
||||
policyChanged := make(chan policyclient.PolicyChange, 1)
|
||||
unregister := policy.RegisterChangeCallback(func(pc policyclient.PolicyChange) { policyChanged <- pc })
|
||||
t.Cleanup(unregister)
|
||||
|
||||
// Now, let's replace the initial store with the new store.
|
||||
|
@ -1,13 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
// Key is a string that uniquely identifies a policy and must remain unchanged
|
||||
// once established and documented for a given policy setting. It may contain
|
||||
// alphanumeric characters and zero or more [KeyPathSeparator]s to group
|
||||
// individual policy settings into categories.
|
||||
type Key string
|
||||
|
||||
// KeyPathSeparator allows logical grouping of policy settings into categories.
|
||||
const KeyPathSeparator = '/'
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
)
|
||||
|
||||
// RawItem contains a raw policy setting value as read from a policy store, or an
|
||||
@ -159,4 +160,4 @@ func (v *RawValue) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
// RawValues is a map of keyed setting values that can be read from a JSON.
|
||||
type RawValues map[Key]RawValue
|
||||
type RawValues map[pkey.Key]RawValue
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
)
|
||||
|
||||
// Scope indicates the broadest scope at which a policy setting may apply,
|
||||
@ -133,7 +134,7 @@ type ValueType interface {
|
||||
|
||||
// Definition defines policy key, scope and value type.
|
||||
type Definition struct {
|
||||
key Key
|
||||
key pkey.Key
|
||||
scope Scope
|
||||
typ Type
|
||||
platforms PlatformList
|
||||
@ -141,12 +142,12 @@ type Definition struct {
|
||||
|
||||
// NewDefinition returns a new [Definition] with the specified
|
||||
// key, scope, type and supported platforms (see [PlatformList]).
|
||||
func NewDefinition(k Key, s Scope, t Type, platforms ...string) *Definition {
|
||||
func NewDefinition(k pkey.Key, s Scope, t Type, platforms ...string) *Definition {
|
||||
return &Definition{key: k, scope: s, typ: t, platforms: platforms}
|
||||
}
|
||||
|
||||
// Key returns a policy setting's identifier.
|
||||
func (d *Definition) Key() Key {
|
||||
func (d *Definition) Key() pkey.Key {
|
||||
if d == nil {
|
||||
return ""
|
||||
}
|
||||
@ -207,7 +208,7 @@ func (d *Definition) Equal(d2 *Definition) bool {
|
||||
}
|
||||
|
||||
// DefinitionMap is a map of setting [Definition] by [Key].
|
||||
type DefinitionMap map[Key]*Definition
|
||||
type DefinitionMap map[pkey.Key]*Definition
|
||||
|
||||
var (
|
||||
definitions lazy.SyncValue[DefinitionMap]
|
||||
@ -223,7 +224,7 @@ var (
|
||||
// invoking any functions that use the registered policy definitions. This
|
||||
// includes calling [Definitions] or [DefinitionOf] directly, or reading any
|
||||
// policy settings via syspolicy.
|
||||
func Register(k Key, s Scope, t Type, platforms ...string) {
|
||||
func Register(k pkey.Key, s Scope, t Type, platforms ...string) {
|
||||
RegisterDefinition(NewDefinition(k, s, t, platforms...))
|
||||
}
|
||||
|
||||
@ -289,7 +290,7 @@ func SetDefinitionsForTest(tb lazy.TB, ds ...*Definition) error {
|
||||
// DefinitionOf returns a setting definition by key,
|
||||
// or [ErrNoSuchKey] if the specified key does not exist,
|
||||
// or an error if there are conflicting policy definitions.
|
||||
func DefinitionOf(k Key) (*Definition, error) {
|
||||
func DefinitionOf(k pkey.Key) (*Definition, error) {
|
||||
ds, err := settingDefinitions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -11,8 +11,11 @@ import (
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
)
|
||||
|
||||
type Key = pkey.Key
|
||||
|
||||
func TestSettingDefinition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -14,34 +14,35 @@ import (
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/util/deephash"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
)
|
||||
|
||||
// Snapshot is an immutable collection of ([Key], [RawItem]) pairs, representing
|
||||
// a set of policy settings applied at a specific moment in time.
|
||||
// A nil pointer to [Snapshot] is valid.
|
||||
type Snapshot struct {
|
||||
m map[Key]RawItem
|
||||
m map[pkey.Key]RawItem
|
||||
sig deephash.Sum // of m
|
||||
summary Summary
|
||||
}
|
||||
|
||||
// NewSnapshot returns a new [Snapshot] with the specified items and options.
|
||||
func NewSnapshot(items map[Key]RawItem, opts ...SummaryOption) *Snapshot {
|
||||
func NewSnapshot(items map[pkey.Key]RawItem, opts ...SummaryOption) *Snapshot {
|
||||
return &Snapshot{m: xmaps.Clone(items), sig: deephash.Hash(&items), summary: SummaryWith(opts...)}
|
||||
}
|
||||
|
||||
// All returns an iterator over policy settings in s. The iteration order is not
|
||||
// specified and is not guaranteed to be the same from one call to the next.
|
||||
func (s *Snapshot) All() iter.Seq2[Key, RawItem] {
|
||||
func (s *Snapshot) All() iter.Seq2[pkey.Key, RawItem] {
|
||||
if s == nil {
|
||||
return func(yield func(Key, RawItem) bool) {}
|
||||
return func(yield func(pkey.Key, RawItem) bool) {}
|
||||
}
|
||||
return maps.All(s.m)
|
||||
}
|
||||
|
||||
// Get returns the value of the policy setting with the specified key
|
||||
// or nil if it is not configured or has an error.
|
||||
func (s *Snapshot) Get(k Key) any {
|
||||
func (s *Snapshot) Get(k pkey.Key) any {
|
||||
v, _ := s.GetErr(k)
|
||||
return v
|
||||
}
|
||||
@ -49,7 +50,7 @@ func (s *Snapshot) Get(k Key) any {
|
||||
// GetErr returns the value of the policy setting with the specified key,
|
||||
// [ErrNotConfigured] if it is not configured, or an error returned by
|
||||
// the policy Store if the policy setting could not be read.
|
||||
func (s *Snapshot) GetErr(k Key) (any, error) {
|
||||
func (s *Snapshot) GetErr(k pkey.Key) (any, error) {
|
||||
if s != nil {
|
||||
if s, ok := s.m[k]; ok {
|
||||
return s.Value(), s.Error()
|
||||
@ -61,7 +62,7 @@ func (s *Snapshot) GetErr(k Key) (any, error) {
|
||||
// GetSetting returns the untyped policy setting with the specified key and true
|
||||
// if a policy setting with such key has been configured;
|
||||
// otherwise, it returns zero, false.
|
||||
func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
|
||||
func (s *Snapshot) GetSetting(k pkey.Key) (setting RawItem, ok bool) {
|
||||
setting, ok = s.m[k]
|
||||
return setting, ok
|
||||
}
|
||||
@ -93,9 +94,9 @@ func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
|
||||
|
||||
// Keys return an iterator over keys in s. The iteration order is not specified
|
||||
// and is not guaranteed to be the same from one call to the next.
|
||||
func (s *Snapshot) Keys() iter.Seq[Key] {
|
||||
func (s *Snapshot) Keys() iter.Seq[pkey.Key] {
|
||||
if s.m == nil {
|
||||
return func(yield func(Key) bool) {}
|
||||
return func(yield func(pkey.Key) bool) {}
|
||||
}
|
||||
return maps.Keys(s.m)
|
||||
}
|
||||
@ -143,8 +144,8 @@ func (s *Snapshot) String() string {
|
||||
|
||||
// snapshotJSON holds JSON-marshallable data for [Snapshot].
|
||||
type snapshotJSON struct {
|
||||
Summary Summary `json:",omitzero"`
|
||||
Settings map[Key]RawItem `json:",omitempty"`
|
||||
Summary Summary `json:",omitzero"`
|
||||
Settings map[pkey.Key]RawItem `json:",omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
@ -208,7 +209,7 @@ func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
|
||||
}
|
||||
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
|
||||
}
|
||||
m := make(map[Key]RawItem, snapshot1.Len()+snapshot2.Len())
|
||||
m := make(map[pkey.Key]RawItem, snapshot1.Len()+snapshot2.Len())
|
||||
xmaps.Copy(m, snapshot1.m)
|
||||
xmaps.Copy(m, snapshot2.m) // snapshot2 has higher precedence
|
||||
return &Snapshot{m, deephash.Hash(&m), SummaryWith(summaryOpts...)}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -22,7 +23,7 @@ var _ Store = (*EnvPolicyStore)(nil)
|
||||
type EnvPolicyStore struct{}
|
||||
|
||||
// ReadString implements [Store].
|
||||
func (s *EnvPolicyStore) ReadString(key setting.Key) (string, error) {
|
||||
func (s *EnvPolicyStore) ReadString(key pkey.Key) (string, error) {
|
||||
_, str, err := s.lookupSettingVariable(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -31,7 +32,7 @@ func (s *EnvPolicyStore) ReadString(key setting.Key) (string, error) {
|
||||
}
|
||||
|
||||
// ReadUInt64 implements [Store].
|
||||
func (s *EnvPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
func (s *EnvPolicyStore) ReadUInt64(key pkey.Key) (uint64, error) {
|
||||
name, str, err := s.lookupSettingVariable(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -47,7 +48,7 @@ func (s *EnvPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
}
|
||||
|
||||
// ReadBoolean implements [Store].
|
||||
func (s *EnvPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
func (s *EnvPolicyStore) ReadBoolean(key pkey.Key) (bool, error) {
|
||||
name, str, err := s.lookupSettingVariable(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -63,7 +64,7 @@ func (s *EnvPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
}
|
||||
|
||||
// ReadStringArray implements [Store].
|
||||
func (s *EnvPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
func (s *EnvPolicyStore) ReadStringArray(key pkey.Key) ([]string, error) {
|
||||
_, str, err := s.lookupSettingVariable(key)
|
||||
if err != nil || str == "" {
|
||||
return nil, err
|
||||
@ -79,7 +80,7 @@ func (s *EnvPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
return res[0:dst], nil
|
||||
}
|
||||
|
||||
func (s *EnvPolicyStore) lookupSettingVariable(key setting.Key) (name, value string, err error) {
|
||||
func (s *EnvPolicyStore) lookupSettingVariable(key pkey.Key) (name, value string, err error) {
|
||||
name, err = keyToEnvVarName(key)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@ -103,7 +104,7 @@ var (
|
||||
//
|
||||
// It's fine to use this in [EnvPolicyStore] without caching variable names since it's not a hot path.
|
||||
// [EnvPolicyStore] is not a [Changeable] policy store, so the conversion will only happen once.
|
||||
func keyToEnvVarName(key setting.Key) (string, error) {
|
||||
func keyToEnvVarName(key pkey.Key) (string, error) {
|
||||
if len(key) == 0 {
|
||||
return "", errEmptyKey
|
||||
}
|
||||
@ -135,7 +136,7 @@ func keyToEnvVarName(key setting.Key) (string, error) {
|
||||
}
|
||||
case isDigit(c):
|
||||
split = currentWord.Len() > 0 && !isDigit(key[i-1])
|
||||
case c == setting.KeyPathSeparator:
|
||||
case c == pkey.KeyPathSeparator:
|
||||
words = append(words, currentWord.String())
|
||||
currentWord.Reset()
|
||||
continue
|
||||
|
@ -11,13 +11,14 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
func TestKeyToEnvVarName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key setting.Key
|
||||
key pkey.Key
|
||||
want string // suffix after "TS_DEBUGSYSPOLICY_"
|
||||
wantErr error
|
||||
}{
|
||||
@ -166,7 +167,7 @@ func TestEnvPolicyStore(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
key setting.Key
|
||||
key pkey.Key
|
||||
lookup func(string) (string, bool)
|
||||
want any
|
||||
wantErr error
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/internal/metrics"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -138,9 +139,9 @@ func (r *Reader) reload(force bool) (*setting.Snapshot, error) {
|
||||
|
||||
metrics.Reset(r.origin)
|
||||
|
||||
var m map[setting.Key]setting.RawItem
|
||||
var m map[pkey.Key]setting.RawItem
|
||||
if lastPolicyCount := r.lastPolicy.Len(); lastPolicyCount > 0 {
|
||||
m = make(map[setting.Key]setting.RawItem, lastPolicyCount)
|
||||
m = make(map[pkey.Key]setting.RawItem, lastPolicyCount)
|
||||
}
|
||||
for _, s := range r.settings {
|
||||
if !r.origin.Scope().IsConfigurableSetting(s) {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -72,7 +73,7 @@ func TestReaderLifecycle(t *testing.T) {
|
||||
initWant: setting.NewSnapshot(nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
addStrings: []TestSetting[string]{TestSettingOf("StringValue", "S1")},
|
||||
addStringLists: []TestSetting[[]string]{TestSettingOf("StringListValue", []string{"S1", "S2", "S3"})},
|
||||
newWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
newWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"StringValue": setting.RawItemWith("S1", nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
"StringListValue": setting.RawItemWith([]string{"S1", "S2", "S3"}, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
}, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
@ -136,7 +137,7 @@ func TestReaderLifecycle(t *testing.T) {
|
||||
TestSettingOf("PreferenceOptionValue", "always"),
|
||||
TestSettingOf("VisibilityValue", "show"),
|
||||
},
|
||||
initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
initWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"DurationValue": setting.RawItemWith(must.Get(time.ParseDuration("2h30m")), nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
"PreferenceOptionValue": setting.RawItemWith(setting.AlwaysByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
"VisibilityValue": setting.RawItemWith(setting.VisibleByPolicy, nil, setting.NewNamedOrigin("Test", setting.DeviceScope)),
|
||||
@ -165,7 +166,7 @@ func TestReaderLifecycle(t *testing.T) {
|
||||
initUInt64s: []TestSetting[uint64]{
|
||||
TestSettingOf[uint64]("VisibilityValue", 42), // type mismatch
|
||||
},
|
||||
initWant: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
initWant: setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"DurationValue1": setting.RawItemWith(nil, setting.NewErrorText("time: invalid duration \"soon\""), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
|
||||
"DurationValue2": setting.RawItemWith(nil, setting.NewErrorText("bang!"), setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
|
||||
"PreferenceOptionValue": setting.RawItemWith(setting.ShowChoiceByPolicy, nil, setting.NewNamedOrigin("Test", setting.CurrentUserScope)),
|
||||
@ -277,7 +278,7 @@ func TestReadingSession(t *testing.T) {
|
||||
t.Fatalf("the session was closed prematurely")
|
||||
}
|
||||
|
||||
want := setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||||
want := setting.NewSnapshot(map[pkey.Key]setting.RawItem{
|
||||
"StringValue": setting.RawItemWith("S1", nil, origin),
|
||||
}, origin)
|
||||
if got := session.GetSettings(); !got.Equal(want) {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"io"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -31,19 +32,19 @@ type Store interface {
|
||||
// ReadString returns the value of a [setting.StringValue] with the specified key,
|
||||
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
|
||||
// an error on failure.
|
||||
ReadString(key setting.Key) (string, error)
|
||||
ReadString(key pkey.Key) (string, error)
|
||||
// ReadUInt64 returns the value of a [setting.IntegerValue] with the specified key,
|
||||
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
|
||||
// an error on failure.
|
||||
ReadUInt64(key setting.Key) (uint64, error)
|
||||
ReadUInt64(key pkey.Key) (uint64, error)
|
||||
// ReadBoolean returns the value of a [setting.BooleanValue] with the specified key,
|
||||
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
|
||||
// an error on failure.
|
||||
ReadBoolean(key setting.Key) (bool, error)
|
||||
ReadBoolean(key pkey.Key) (bool, error)
|
||||
// ReadStringArray returns the value of a [setting.StringListValue] with the specified key,
|
||||
// an [setting.ErrNotConfigured] if the policy setting is not configured, or
|
||||
// an error on failure.
|
||||
ReadStringArray(key setting.Key) ([]string, error)
|
||||
ReadStringArray(key pkey.Key) ([]string, error)
|
||||
}
|
||||
|
||||
// Lockable is an optional interface that [Store] implementations may support.
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/winutil/gp"
|
||||
)
|
||||
@ -238,7 +239,7 @@ func (ps *PlatformPolicyStore) onChange() {
|
||||
|
||||
// ReadString retrieves a string policy with the specified key.
|
||||
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
|
||||
func (ps *PlatformPolicyStore) ReadString(key setting.Key) (val string, err error) {
|
||||
func (ps *PlatformPolicyStore) ReadString(key pkey.Key) (val string, err error) {
|
||||
return getPolicyValue(ps, key,
|
||||
func(key registry.Key, valueName string) (string, error) {
|
||||
val, _, err := key.GetStringValue(valueName)
|
||||
@ -248,7 +249,7 @@ func (ps *PlatformPolicyStore) ReadString(key setting.Key) (val string, err erro
|
||||
|
||||
// ReadUInt64 retrieves an integer policy with the specified key.
|
||||
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
|
||||
func (ps *PlatformPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
func (ps *PlatformPolicyStore) ReadUInt64(key pkey.Key) (uint64, error) {
|
||||
return getPolicyValue(ps, key,
|
||||
func(key registry.Key, valueName string) (uint64, error) {
|
||||
val, _, err := key.GetIntegerValue(valueName)
|
||||
@ -258,7 +259,7 @@ func (ps *PlatformPolicyStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
|
||||
// ReadBoolean retrieves a boolean policy with the specified key.
|
||||
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
|
||||
func (ps *PlatformPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
func (ps *PlatformPolicyStore) ReadBoolean(key pkey.Key) (bool, error) {
|
||||
return getPolicyValue(ps, key,
|
||||
func(key registry.Key, valueName string) (bool, error) {
|
||||
val, _, err := key.GetIntegerValue(valueName)
|
||||
@ -271,7 +272,7 @@ func (ps *PlatformPolicyStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
|
||||
// ReadString retrieves a multi-string policy with the specified key.
|
||||
// It returns [setting.ErrNotConfigured] if the policy setting does not exist.
|
||||
func (ps *PlatformPolicyStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
func (ps *PlatformPolicyStore) ReadStringArray(key pkey.Key) ([]string, error) {
|
||||
return getPolicyValue(ps, key,
|
||||
func(key registry.Key, valueName string) ([]string, error) {
|
||||
val, _, err := key.GetStringsValue(valueName)
|
||||
@ -318,16 +319,16 @@ func (ps *PlatformPolicyStore) ReadStringArray(key setting.Key) ([]string, error
|
||||
// while everything preceding it is considered a subpath (relative to the {HKLM,HKCU}\Software\Policies\Tailscale key).
|
||||
// If there are no [setting.KeyPathSeparator]s in the key, the policy setting value
|
||||
// is meant to be stored directly under {HKLM,HKCU}\Software\Policies\Tailscale.
|
||||
func splitSettingKey(key setting.Key) (path, valueName string) {
|
||||
if idx := strings.LastIndexByte(string(key), setting.KeyPathSeparator); idx != -1 {
|
||||
path = strings.ReplaceAll(string(key[:idx]), string(setting.KeyPathSeparator), `\`)
|
||||
func splitSettingKey(key pkey.Key) (path, valueName string) {
|
||||
if idx := strings.LastIndexByte(string(key), pkey.KeyPathSeparator); idx != -1 {
|
||||
path = strings.ReplaceAll(string(key[:idx]), string(pkey.KeyPathSeparator), `\`)
|
||||
valueName = string(key[idx+1:])
|
||||
return path, valueName
|
||||
}
|
||||
return "", string(key)
|
||||
}
|
||||
|
||||
func getPolicyValue[T any](ps *PlatformPolicyStore, key setting.Key, getter registryValueGetter[T]) (T, error) {
|
||||
func getPolicyValue[T any](ps *PlatformPolicyStore, key pkey.Key, getter registryValueGetter[T]) (T, error) {
|
||||
var zero T
|
||||
|
||||
ps.mu.Lock()
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/util/cibuild"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/winutil"
|
||||
"tailscale.com/util/winutil/gp"
|
||||
@ -31,7 +32,7 @@ import (
|
||||
type subkeyStrings []string
|
||||
|
||||
type testPolicyValue struct {
|
||||
name setting.Key
|
||||
name pkey.Key
|
||||
value any
|
||||
}
|
||||
|
||||
@ -100,7 +101,7 @@ func TestReadPolicyStore(t *testing.T) {
|
||||
t.Skipf("test requires running as elevated user")
|
||||
}
|
||||
tests := []struct {
|
||||
name setting.Key
|
||||
name pkey.Key
|
||||
newValue any
|
||||
legacyValue any
|
||||
want any
|
||||
@ -269,7 +270,7 @@ func TestPolicyStoreChangeNotifications(t *testing.T) {
|
||||
func TestSplitSettingKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key setting.Key
|
||||
key pkey.Key
|
||||
wantPath string
|
||||
wantValue string
|
||||
}{
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/slicesx"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
@ -31,7 +32,7 @@ type TestValueType interface {
|
||||
// TestSetting is a policy setting in a [TestStore].
|
||||
type TestSetting[T TestValueType] struct {
|
||||
// Key is the setting's unique identifier.
|
||||
Key setting.Key
|
||||
Key pkey.Key
|
||||
// Error is the error to be returned by the [TestStore] when reading
|
||||
// a policy setting with the specified key.
|
||||
Error error
|
||||
@ -43,20 +44,20 @@ type TestSetting[T TestValueType] struct {
|
||||
|
||||
// TestSettingOf returns a [TestSetting] representing a policy setting
|
||||
// configured with the specified key and value.
|
||||
func TestSettingOf[T TestValueType](key setting.Key, value T) TestSetting[T] {
|
||||
func TestSettingOf[T TestValueType](key pkey.Key, value T) TestSetting[T] {
|
||||
return TestSetting[T]{Key: key, Value: value}
|
||||
}
|
||||
|
||||
// TestSettingWithError returns a [TestSetting] representing a policy setting
|
||||
// with the specified key and error.
|
||||
func TestSettingWithError[T TestValueType](key setting.Key, err error) TestSetting[T] {
|
||||
func TestSettingWithError[T TestValueType](key pkey.Key, err error) TestSetting[T] {
|
||||
return TestSetting[T]{Key: key, Error: err}
|
||||
}
|
||||
|
||||
// testReadOperation describes a single policy setting read operation.
|
||||
type testReadOperation struct {
|
||||
// Key is the setting's unique identifier.
|
||||
Key setting.Key
|
||||
Key pkey.Key
|
||||
// Type is a value type of a read operation.
|
||||
// [setting.BooleanValue], [setting.IntegerValue], [setting.StringValue] or [setting.StringListValue]
|
||||
Type setting.Type
|
||||
@ -65,7 +66,7 @@ type testReadOperation struct {
|
||||
// TestExpectedReads is the number of read operations with the specified details.
|
||||
type TestExpectedReads struct {
|
||||
// Key is the setting's unique identifier.
|
||||
Key setting.Key
|
||||
Key pkey.Key
|
||||
// Type is a value type of a read operation.
|
||||
// [setting.BooleanValue], [setting.IntegerValue], [setting.StringValue] or [setting.StringListValue]
|
||||
Type setting.Type
|
||||
@ -87,8 +88,8 @@ type TestStore struct {
|
||||
storeLockCount atomic.Int32
|
||||
|
||||
mu sync.RWMutex
|
||||
suspendCount int // change callback are suspended if > 0
|
||||
mr, mw map[setting.Key]any // maps for reading and writing; they're the same unless the store is suspended.
|
||||
suspendCount int // change callback are suspended if > 0
|
||||
mr, mw map[pkey.Key]any // maps for reading and writing; they're the same unless the store is suspended.
|
||||
cbs set.HandleSet[func()]
|
||||
closed bool
|
||||
|
||||
@ -99,7 +100,7 @@ type TestStore struct {
|
||||
// NewTestStore returns a new [TestStore].
|
||||
// The tb will be used to report coding errors detected by the [TestStore].
|
||||
func NewTestStore(tb internal.TB) *TestStore {
|
||||
m := make(map[setting.Key]any)
|
||||
m := make(map[pkey.Key]any)
|
||||
store := &TestStore{
|
||||
tb: tb,
|
||||
done: make(chan struct{}),
|
||||
@ -155,7 +156,7 @@ func (s *TestStore) RegisterChangeCallback(callback func()) (unregister func(),
|
||||
}
|
||||
|
||||
// ReadString implements [Store].
|
||||
func (s *TestStore) ReadString(key setting.Key) (string, error) {
|
||||
func (s *TestStore) ReadString(key pkey.Key) (string, error) {
|
||||
defer s.recordRead(key, setting.StringValue)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@ -174,7 +175,7 @@ func (s *TestStore) ReadString(key setting.Key) (string, error) {
|
||||
}
|
||||
|
||||
// ReadUInt64 implements [Store].
|
||||
func (s *TestStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
func (s *TestStore) ReadUInt64(key pkey.Key) (uint64, error) {
|
||||
defer s.recordRead(key, setting.IntegerValue)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@ -193,7 +194,7 @@ func (s *TestStore) ReadUInt64(key setting.Key) (uint64, error) {
|
||||
}
|
||||
|
||||
// ReadBoolean implements [Store].
|
||||
func (s *TestStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
func (s *TestStore) ReadBoolean(key pkey.Key) (bool, error) {
|
||||
defer s.recordRead(key, setting.BooleanValue)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@ -212,7 +213,7 @@ func (s *TestStore) ReadBoolean(key setting.Key) (bool, error) {
|
||||
}
|
||||
|
||||
// ReadStringArray implements [Store].
|
||||
func (s *TestStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
func (s *TestStore) ReadStringArray(key pkey.Key) ([]string, error) {
|
||||
defer s.recordRead(key, setting.StringListValue)
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@ -230,7 +231,7 @@ func (s *TestStore) ReadStringArray(key setting.Key) ([]string, error) {
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (s *TestStore) recordRead(key setting.Key, typ setting.Type) {
|
||||
func (s *TestStore) recordRead(key pkey.Key, typ setting.Type) {
|
||||
s.readsMu.Lock()
|
||||
op := testReadOperation{key, typ}
|
||||
num := s.reads[op]
|
||||
@ -318,15 +319,15 @@ func (s *TestStore) Resume() {
|
||||
// SetBooleans sets the specified boolean settings in s.
|
||||
func (s *TestStore) SetBooleans(settings ...TestSetting[bool]) {
|
||||
s.storeLock.Lock()
|
||||
for _, setting := range settings {
|
||||
if setting.Key == "" {
|
||||
for _, ts := range settings {
|
||||
if ts.Key == "" {
|
||||
s.tb.Fatal("empty keys disallowed")
|
||||
}
|
||||
s.mu.Lock()
|
||||
if setting.Error != nil {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Error))
|
||||
if ts.Error != nil {
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Error))
|
||||
} else {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Value))
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Value))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@ -337,15 +338,15 @@ func (s *TestStore) SetBooleans(settings ...TestSetting[bool]) {
|
||||
// SetUInt64s sets the specified integer settings in s.
|
||||
func (s *TestStore) SetUInt64s(settings ...TestSetting[uint64]) {
|
||||
s.storeLock.Lock()
|
||||
for _, setting := range settings {
|
||||
if setting.Key == "" {
|
||||
for _, ts := range settings {
|
||||
if ts.Key == "" {
|
||||
s.tb.Fatal("empty keys disallowed")
|
||||
}
|
||||
s.mu.Lock()
|
||||
if setting.Error != nil {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Error))
|
||||
if ts.Error != nil {
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Error))
|
||||
} else {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Value))
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Value))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@ -356,15 +357,15 @@ func (s *TestStore) SetUInt64s(settings ...TestSetting[uint64]) {
|
||||
// SetStrings sets the specified string settings in s.
|
||||
func (s *TestStore) SetStrings(settings ...TestSetting[string]) {
|
||||
s.storeLock.Lock()
|
||||
for _, setting := range settings {
|
||||
if setting.Key == "" {
|
||||
for _, ts := range settings {
|
||||
if ts.Key == "" {
|
||||
s.tb.Fatal("empty keys disallowed")
|
||||
}
|
||||
s.mu.Lock()
|
||||
if setting.Error != nil {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Error))
|
||||
if ts.Error != nil {
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Error))
|
||||
} else {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Value))
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Value))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@ -375,15 +376,15 @@ func (s *TestStore) SetStrings(settings ...TestSetting[string]) {
|
||||
// SetStrings sets the specified string list settings in s.
|
||||
func (s *TestStore) SetStringLists(settings ...TestSetting[[]string]) {
|
||||
s.storeLock.Lock()
|
||||
for _, setting := range settings {
|
||||
if setting.Key == "" {
|
||||
for _, ts := range settings {
|
||||
if ts.Key == "" {
|
||||
s.tb.Fatal("empty keys disallowed")
|
||||
}
|
||||
s.mu.Lock()
|
||||
if setting.Error != nil {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Error))
|
||||
if ts.Error != nil {
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Error))
|
||||
} else {
|
||||
mak.Set(&s.mw, setting.Key, any(setting.Value))
|
||||
mak.Set(&s.mw, ts.Key, any(ts.Value))
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@ -392,7 +393,7 @@ func (s *TestStore) SetStringLists(settings ...TestSetting[[]string]) {
|
||||
}
|
||||
|
||||
// Delete deletes the specified settings from s.
|
||||
func (s *TestStore) Delete(keys ...setting.Key) {
|
||||
func (s *TestStore) Delete(keys ...pkey.Key) {
|
||||
s.storeLock.Lock()
|
||||
for _, key := range keys {
|
||||
s.mu.Lock()
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
@ -47,6 +48,7 @@ func RegisterStore(name string, scope setting.PolicyScope, store source.Store) (
|
||||
|
||||
// MustRegisterStoreForTest is like [rsop.RegisterStoreForTest], but it fails the test if the store could not be registered.
|
||||
func MustRegisterStoreForTest(tb TB, name string, scope setting.PolicyScope, store source.Store) *rsop.StoreRegistration {
|
||||
tb.Skip("XXXX delete MustRegisterStoreForTest")
|
||||
tb.Helper()
|
||||
reg, err := rsop.RegisterStoreForTest(tb, name, scope, store)
|
||||
if err != nil {
|
||||
@ -57,25 +59,25 @@ func MustRegisterStoreForTest(tb TB, name string, scope setting.PolicyScope, sto
|
||||
|
||||
// GetString returns a string policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
func GetString(key Key, defaultValue string) (string, error) {
|
||||
func GetString(key pkey.Key, defaultValue string) (string, error) {
|
||||
return getCurrentPolicySettingValue(key, defaultValue)
|
||||
}
|
||||
|
||||
// GetUint64 returns a numeric policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
func GetUint64(key Key, defaultValue uint64) (uint64, error) {
|
||||
func GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
|
||||
return getCurrentPolicySettingValue(key, defaultValue)
|
||||
}
|
||||
|
||||
// GetBoolean returns a boolean policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
func GetBoolean(key Key, defaultValue bool) (bool, error) {
|
||||
func GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
|
||||
return getCurrentPolicySettingValue(key, defaultValue)
|
||||
}
|
||||
|
||||
// GetStringArray returns a multi-string policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
func GetStringArray(key Key, defaultValue []string) ([]string, error) {
|
||||
func GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
|
||||
return getCurrentPolicySettingValue(key, defaultValue)
|
||||
}
|
||||
|
||||
@ -85,7 +87,7 @@ func GetStringArray(key Key, defaultValue []string) ([]string, error) {
|
||||
// the authority to set. It describes user-decides/always/never options, where
|
||||
// "always" and "never" remove the user's ability to make a selection. If not
|
||||
// present or set to a different value, "user-decides" is the default.
|
||||
func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
|
||||
func GetPreferenceOption(name pkey.Key) (setting.PreferenceOption, error) {
|
||||
return getCurrentPolicySettingValue(name, setting.ShowChoiceByPolicy)
|
||||
}
|
||||
|
||||
@ -94,7 +96,7 @@ func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
|
||||
// for UI elements. The registry value should be a string set to "show" (return
|
||||
// true) or "hide" (return true). If not present or set to a different value,
|
||||
// "show" (return false) is the default.
|
||||
func GetVisibility(name Key) (setting.Visibility, error) {
|
||||
func GetVisibility(name pkey.Key) (setting.Visibility, error) {
|
||||
return getCurrentPolicySettingValue(name, setting.VisibleByPolicy)
|
||||
}
|
||||
|
||||
@ -103,7 +105,7 @@ func GetVisibility(name Key) (setting.Visibility, error) {
|
||||
// action. The registry value should be a string that time.ParseDuration
|
||||
// understands. If the registry value is "" or can not be processed,
|
||||
// defaultValue is returned instead.
|
||||
func GetDuration(name Key, defaultValue time.Duration) (time.Duration, error) {
|
||||
func GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
|
||||
d, err := getCurrentPolicySettingValue(name, defaultValue)
|
||||
if err != nil {
|
||||
return d, err
|
||||
@ -128,7 +130,7 @@ func RegisterChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), er
|
||||
// specified by its key from the [rsop.Policy] of the [setting.DefaultScope]. It
|
||||
// returns def if the policy setting is not configured, or an error if it has
|
||||
// an error or could not be converted to the specified type T.
|
||||
func getCurrentPolicySettingValue[T setting.ValueType](key Key, def T) (T, error) {
|
||||
func getCurrentPolicySettingValue[T setting.ValueType](key pkey.Key, def T) (T, error) {
|
||||
effective, err := rsop.PolicyFor(setting.DefaultScope())
|
||||
if err != nil {
|
||||
return def, err
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/internal/metrics"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
)
|
||||
@ -31,7 +32,7 @@ func TestGetString(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "read existing value",
|
||||
key: AdminConsoleVisibility,
|
||||
key: pkey.AdminConsoleVisibility,
|
||||
handlerValue: "hide",
|
||||
wantValue: "hide",
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -41,13 +42,13 @@ func TestGetString(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: EnableServerMode,
|
||||
key: pkey.EnableServerMode,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "read non-existing value, non-blank default",
|
||||
key: EnableServerMode,
|
||||
key: pkey.EnableServerMode,
|
||||
handlerError: ErrNotConfigured,
|
||||
defaultValue: "test",
|
||||
wantValue: "test",
|
||||
@ -55,7 +56,7 @@ func TestGetString(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "reading value returns other error",
|
||||
key: NetworkDevicesVisibility,
|
||||
key: pkey.NetworkDevicesVisibility,
|
||||
handlerError: someOtherError,
|
||||
wantError: someOtherError,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -111,27 +112,27 @@ func TestGetUint64(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "read existing value",
|
||||
key: LogSCMInteractions,
|
||||
key: pkey.LogSCMInteractions,
|
||||
handlerValue: 1,
|
||||
wantValue: 1,
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: LogSCMInteractions,
|
||||
key: pkey.LogSCMInteractions,
|
||||
handlerValue: 0,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: 0,
|
||||
},
|
||||
{
|
||||
name: "read non-existing value, non-zero default",
|
||||
key: LogSCMInteractions,
|
||||
key: pkey.LogSCMInteractions,
|
||||
defaultValue: 2,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: 2,
|
||||
},
|
||||
{
|
||||
name: "reading value returns other error",
|
||||
key: FlushDNSOnSessionUnlock,
|
||||
key: pkey.FlushDNSOnSessionUnlock,
|
||||
handlerError: someOtherError,
|
||||
wantError: someOtherError,
|
||||
},
|
||||
@ -178,7 +179,7 @@ func TestGetBoolean(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "read existing value",
|
||||
key: FlushDNSOnSessionUnlock,
|
||||
key: pkey.FlushDNSOnSessionUnlock,
|
||||
handlerValue: true,
|
||||
wantValue: true,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -188,14 +189,14 @@ func TestGetBoolean(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: LogSCMInteractions,
|
||||
key: pkey.LogSCMInteractions,
|
||||
handlerValue: false,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: false,
|
||||
},
|
||||
{
|
||||
name: "reading value returns other error",
|
||||
key: FlushDNSOnSessionUnlock,
|
||||
key: pkey.FlushDNSOnSessionUnlock,
|
||||
handlerError: someOtherError,
|
||||
wantError: someOtherError, // expect error...
|
||||
defaultValue: true,
|
||||
@ -253,7 +254,7 @@ func TestGetPreferenceOption(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "always by policy",
|
||||
key: EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
handlerValue: "always",
|
||||
wantValue: setting.AlwaysByPolicy,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -263,7 +264,7 @@ func TestGetPreferenceOption(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "never by policy",
|
||||
key: EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
handlerValue: "never",
|
||||
wantValue: setting.NeverByPolicy,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -273,7 +274,7 @@ func TestGetPreferenceOption(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "use default",
|
||||
key: EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
handlerValue: "",
|
||||
wantValue: setting.ShowChoiceByPolicy,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -283,13 +284,13 @@ func TestGetPreferenceOption(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: setting.ShowChoiceByPolicy,
|
||||
},
|
||||
{
|
||||
name: "other error is returned",
|
||||
key: EnableIncomingConnections,
|
||||
key: pkey.EnableIncomingConnections,
|
||||
handlerError: someOtherError,
|
||||
wantValue: setting.ShowChoiceByPolicy,
|
||||
wantError: someOtherError,
|
||||
@ -346,7 +347,7 @@ func TestGetVisibility(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "hidden by policy",
|
||||
key: AdminConsoleVisibility,
|
||||
key: pkey.AdminConsoleVisibility,
|
||||
handlerValue: "hide",
|
||||
wantValue: setting.HiddenByPolicy,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -356,7 +357,7 @@ func TestGetVisibility(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "visibility default",
|
||||
key: AdminConsoleVisibility,
|
||||
key: pkey.AdminConsoleVisibility,
|
||||
handlerValue: "show",
|
||||
wantValue: setting.VisibleByPolicy,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -366,14 +367,14 @@ func TestGetVisibility(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: AdminConsoleVisibility,
|
||||
key: pkey.AdminConsoleVisibility,
|
||||
handlerValue: "show",
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: setting.VisibleByPolicy,
|
||||
},
|
||||
{
|
||||
name: "other error is returned",
|
||||
key: AdminConsoleVisibility,
|
||||
key: pkey.AdminConsoleVisibility,
|
||||
handlerValue: "show",
|
||||
handlerError: someOtherError,
|
||||
wantValue: setting.VisibleByPolicy,
|
||||
@ -432,7 +433,7 @@ func TestGetDuration(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "read existing value",
|
||||
key: KeyExpirationNoticeTime,
|
||||
key: pkey.KeyExpirationNoticeTime,
|
||||
handlerValue: "2h",
|
||||
wantValue: 2 * time.Hour,
|
||||
defaultValue: 24 * time.Hour,
|
||||
@ -443,7 +444,7 @@ func TestGetDuration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid duration value",
|
||||
key: KeyExpirationNoticeTime,
|
||||
key: pkey.KeyExpirationNoticeTime,
|
||||
handlerValue: "-20",
|
||||
wantValue: 24 * time.Hour,
|
||||
wantError: errors.New(`time: missing unit in duration "-20"`),
|
||||
@ -455,21 +456,21 @@ func TestGetDuration(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: KeyExpirationNoticeTime,
|
||||
key: pkey.KeyExpirationNoticeTime,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: 24 * time.Hour,
|
||||
defaultValue: 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "read non-existing value different default",
|
||||
key: KeyExpirationNoticeTime,
|
||||
key: pkey.KeyExpirationNoticeTime,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantValue: 0 * time.Second,
|
||||
defaultValue: 0 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "other error is returned",
|
||||
key: KeyExpirationNoticeTime,
|
||||
key: pkey.KeyExpirationNoticeTime,
|
||||
handlerError: someOtherError,
|
||||
wantValue: 24 * time.Hour,
|
||||
wantError: someOtherError,
|
||||
@ -528,7 +529,7 @@ func TestGetStringArray(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "read existing value",
|
||||
key: AllowedSuggestedExitNodes,
|
||||
key: pkey.AllowedSuggestedExitNodes,
|
||||
handlerValue: []string{"foo", "bar"},
|
||||
wantValue: []string{"foo", "bar"},
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -538,13 +539,13 @@ func TestGetStringArray(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read non-existing value",
|
||||
key: AllowedSuggestedExitNodes,
|
||||
key: pkey.AllowedSuggestedExitNodes,
|
||||
handlerError: ErrNotConfigured,
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
name: "read non-existing value, non nil default",
|
||||
key: AllowedSuggestedExitNodes,
|
||||
key: pkey.AllowedSuggestedExitNodes,
|
||||
handlerError: ErrNotConfigured,
|
||||
defaultValue: []string{"foo", "bar"},
|
||||
wantValue: []string{"foo", "bar"},
|
||||
@ -552,7 +553,7 @@ func TestGetStringArray(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "reading value returns other error",
|
||||
key: AllowedSuggestedExitNodes,
|
||||
key: pkey.AllowedSuggestedExitNodes,
|
||||
handlerError: someOtherError,
|
||||
wantError: someOtherError,
|
||||
wantMetrics: []metrics.TestState{
|
||||
@ -606,11 +607,11 @@ func BenchmarkGetString(b *testing.B) {
|
||||
RegisterWellKnownSettingsForTest(b)
|
||||
|
||||
wantControlURL := "https://login.tailscale.com"
|
||||
registerSingleSettingStoreForTest(b, source.TestSettingOf(ControlURL, wantControlURL))
|
||||
registerSingleSettingStoreForTest(b, source.TestSettingOf(pkey.ControlURL, wantControlURL))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
gotControlURL, _ := GetString(ControlURL, "https://controlplane.tailscale.com")
|
||||
gotControlURL, _ := GetString(pkey.ControlURL, "https://controlplane.tailscale.com")
|
||||
if gotControlURL != wantControlURL {
|
||||
b.Fatalf("got %v; want %v", gotControlURL, wantControlURL)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user