Update dependencies
This commit is contained in:
122
vendor/tailscale.com/util/syspolicy/caching_handler.go
generated
vendored
Normal file
122
vendor/tailscale.com/util/syspolicy/caching_handler.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CachingHandler is a handler that reads policies from an underlying handler the first time each key is requested
|
||||
// and permanently caches the result unless there is an error. If there is an ErrNoSuchKey error, that result is cached,
|
||||
// otherwise the actual error is returned and the next read for that key will retry using the handler.
|
||||
type CachingHandler struct {
|
||||
mu sync.Mutex
|
||||
strings map[string]string
|
||||
uint64s map[string]uint64
|
||||
bools map[string]bool
|
||||
strArrs map[string][]string
|
||||
notFound map[string]bool
|
||||
handler Handler
|
||||
}
|
||||
|
||||
// NewCachingHandler creates a CachingHandler given a handler.
|
||||
func NewCachingHandler(handler Handler) *CachingHandler {
|
||||
return &CachingHandler{
|
||||
handler: handler,
|
||||
strings: make(map[string]string),
|
||||
uint64s: make(map[string]uint64),
|
||||
bools: make(map[string]bool),
|
||||
strArrs: make(map[string][]string),
|
||||
notFound: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadString reads the policy settings value string given the key.
|
||||
// ReadString first reads from the handler's cache before resorting to using the handler.
|
||||
func (ch *CachingHandler) ReadString(key string) (string, error) {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
if val, ok := ch.strings[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
if notFound := ch.notFound[key]; notFound {
|
||||
return "", ErrNoSuchKey
|
||||
}
|
||||
val, err := ch.handler.ReadString(key)
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
ch.notFound[key] = true
|
||||
return "", err
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ch.strings[key] = val
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ReadUInt64 reads the policy settings uint64 value given the key.
|
||||
// ReadUInt64 first reads from the handler's cache before resorting to using the handler.
|
||||
func (ch *CachingHandler) ReadUInt64(key string) (uint64, error) {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
if val, ok := ch.uint64s[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
if notFound := ch.notFound[key]; notFound {
|
||||
return 0, ErrNoSuchKey
|
||||
}
|
||||
val, err := ch.handler.ReadUInt64(key)
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
ch.notFound[key] = true
|
||||
return 0, err
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ch.uint64s[key] = val
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ReadBoolean reads the policy settings boolean value given the key.
|
||||
// ReadBoolean first reads from the handler's cache before resorting to using the handler.
|
||||
func (ch *CachingHandler) ReadBoolean(key string) (bool, error) {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
if val, ok := ch.bools[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
if notFound := ch.notFound[key]; notFound {
|
||||
return false, ErrNoSuchKey
|
||||
}
|
||||
val, err := ch.handler.ReadBoolean(key)
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
ch.notFound[key] = true
|
||||
return false, err
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ch.bools[key] = val
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ReadBoolean reads the policy settings boolean value given the key.
|
||||
// ReadBoolean first reads from the handler's cache before resorting to using the handler.
|
||||
func (ch *CachingHandler) ReadStringArray(key string) ([]string, error) {
|
||||
ch.mu.Lock()
|
||||
defer ch.mu.Unlock()
|
||||
if val, ok := ch.strArrs[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
if notFound := ch.notFound[key]; notFound {
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
val, err := ch.handler.ReadStringArray(key)
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
ch.notFound[key] = true
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch.strArrs[key] = val
|
||||
return val, nil
|
||||
}
|
||||
83
vendor/tailscale.com/util/syspolicy/handler.go
generated
vendored
Normal file
83
vendor/tailscale.com/util/syspolicy/handler.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
handlerUsed atomic.Bool
|
||||
handler Handler = defaultHandler{}
|
||||
)
|
||||
|
||||
// Handler reads system policies from OS-specific storage.
|
||||
type Handler interface {
|
||||
// ReadString reads the policy setting's string value for the given key.
|
||||
// It should return ErrNoSuchKey if the key does not have a value set.
|
||||
ReadString(key string) (string, error)
|
||||
// ReadUInt64 reads the policy setting's uint64 value for the given key.
|
||||
// It should return ErrNoSuchKey if the key does not have a value set.
|
||||
ReadUInt64(key string) (uint64, error)
|
||||
// ReadBool reads the policy setting's boolean value for the given key.
|
||||
// It should return ErrNoSuchKey if the key does not have a value set.
|
||||
ReadBoolean(key string) (bool, error)
|
||||
// ReadStringArray reads the policy setting's string array value for the given key.
|
||||
// It should return ErrNoSuchKey if the key does not have a value set.
|
||||
ReadStringArray(key string) ([]string, error)
|
||||
}
|
||||
|
||||
// ErrNoSuchKey is returned by a Handler when the specified key does not have a
|
||||
// value set.
|
||||
var ErrNoSuchKey = errors.New("no such key")
|
||||
|
||||
// defaultHandler is the catch all syspolicy type for anything that isn't windows or apple.
|
||||
type defaultHandler struct{}
|
||||
|
||||
func (defaultHandler) ReadString(_ string) (string, error) {
|
||||
return "", ErrNoSuchKey
|
||||
}
|
||||
|
||||
func (defaultHandler) ReadUInt64(_ string) (uint64, error) {
|
||||
return 0, ErrNoSuchKey
|
||||
}
|
||||
|
||||
func (defaultHandler) ReadBoolean(_ string) (bool, error) {
|
||||
return false, ErrNoSuchKey
|
||||
}
|
||||
|
||||
func (defaultHandler) ReadStringArray(_ string) ([]string, error) {
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
|
||||
// markHandlerInUse is called before handler methods are called.
|
||||
func markHandlerInUse() {
|
||||
handlerUsed.Store(true)
|
||||
}
|
||||
|
||||
// RegisterHandler initializes the policy handler and ensures registration will happen once.
|
||||
func RegisterHandler(h Handler) {
|
||||
// Technically this assignment is not concurrency safe, but in the
|
||||
// event that there was any risk of a data race, we will panic due to
|
||||
// the CompareAndSwap failing.
|
||||
handler = h
|
||||
if !handlerUsed.CompareAndSwap(false, true) {
|
||||
panic("handler was already used before registration")
|
||||
}
|
||||
}
|
||||
|
||||
// TB is a subset of testing.TB that we use to set up test helpers.
|
||||
// It's defined here to avoid pulling in the testing package.
|
||||
type TB interface {
|
||||
Helper()
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
func SetHandlerForTest(tb TB, h Handler) {
|
||||
tb.Helper()
|
||||
oldHandler := handler
|
||||
handler = h
|
||||
tb.Cleanup(func() { handler = oldHandler })
|
||||
}
|
||||
105
vendor/tailscale.com/util/syspolicy/handler_windows.go
generated
vendored
Normal file
105
vendor/tailscale.com/util/syspolicy/handler_windows.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/winutil"
|
||||
)
|
||||
|
||||
var (
|
||||
windowsErrors = clientmetric.NewCounter("windows_syspolicy_errors")
|
||||
windowsAny = clientmetric.NewGauge("windows_syspolicy_any")
|
||||
)
|
||||
|
||||
type windowsHandler struct{}
|
||||
|
||||
func init() {
|
||||
RegisterHandler(NewCachingHandler(windowsHandler{}))
|
||||
|
||||
keyList := []struct {
|
||||
isSet func(Key) bool
|
||||
keys []Key
|
||||
}{
|
||||
{
|
||||
isSet: func(k Key) bool {
|
||||
_, err := handler.ReadString(string(k))
|
||||
return err == nil
|
||||
},
|
||||
keys: stringKeys,
|
||||
},
|
||||
{
|
||||
isSet: func(k Key) bool {
|
||||
_, err := handler.ReadBoolean(string(k))
|
||||
return err == nil
|
||||
},
|
||||
keys: boolKeys,
|
||||
},
|
||||
{
|
||||
isSet: func(k Key) bool {
|
||||
_, err := handler.ReadUInt64(string(k))
|
||||
return err == nil
|
||||
},
|
||||
keys: uint64Keys,
|
||||
},
|
||||
}
|
||||
|
||||
var anySet bool
|
||||
for _, l := range keyList {
|
||||
for _, k := range l.keys {
|
||||
if !l.isSet(k) {
|
||||
continue
|
||||
}
|
||||
clientmetric.NewGauge(fmt.Sprintf("windows_syspolicy_%s", k)).Set(1)
|
||||
anySet = true
|
||||
}
|
||||
}
|
||||
if anySet {
|
||||
windowsAny.Set(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (windowsHandler) ReadString(key string) (string, error) {
|
||||
s, err := winutil.GetPolicyString(key)
|
||||
if errors.Is(err, winutil.ErrNoValue) {
|
||||
err = ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
windowsErrors.Add(1)
|
||||
}
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (windowsHandler) ReadUInt64(key string) (uint64, error) {
|
||||
value, err := winutil.GetPolicyInteger(key)
|
||||
if errors.Is(err, winutil.ErrNoValue) {
|
||||
err = ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
windowsErrors.Add(1)
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (windowsHandler) ReadBoolean(key string) (bool, error) {
|
||||
value, err := winutil.GetPolicyInteger(key)
|
||||
if errors.Is(err, winutil.ErrNoValue) {
|
||||
err = ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
windowsErrors.Add(1)
|
||||
}
|
||||
return value != 0, err
|
||||
}
|
||||
|
||||
func (windowsHandler) ReadStringArray(key string) ([]string, error) {
|
||||
value, err := winutil.GetPolicyStringArray(key)
|
||||
if errors.Is(err, winutil.ErrNoValue) {
|
||||
err = ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
windowsErrors.Add(1)
|
||||
}
|
||||
return value, err
|
||||
}
|
||||
63
vendor/tailscale.com/util/syspolicy/internal/internal.go
generated
vendored
Normal file
63
vendor/tailscale.com/util/syspolicy/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package internal contains miscellaneous functions and types
|
||||
// that are internal to the syspolicy packages.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
// OSForTesting is the operating system override used for testing.
|
||||
// It follows the same naming convention as [version.OS].
|
||||
var OSForTesting lazy.SyncValue[string]
|
||||
|
||||
// OS is like [version.OS], but supports a test hook.
|
||||
func OS() string {
|
||||
return OSForTesting.Get(version.OS)
|
||||
}
|
||||
|
||||
// TB is a subset of testing.TB that we use to set up test helpers.
|
||||
// It's defined here to avoid pulling in the testing package.
|
||||
type TB interface {
|
||||
Helper()
|
||||
Cleanup(func())
|
||||
Logf(format string, args ...any)
|
||||
Error(args ...any)
|
||||
Errorf(format string, args ...any)
|
||||
Fatal(args ...any)
|
||||
Fatalf(format string, args ...any)
|
||||
}
|
||||
|
||||
// EqualJSONForTest compares the JSON in j1 and j2 for semantic equality.
|
||||
// It returns "", "", true if j1 and j2 are equal. Otherwise, it returns
|
||||
// indented versions of j1 and j2 and false.
|
||||
func EqualJSONForTest(tb TB, j1, j2 jsontext.Value) (s1, s2 string, equal bool) {
|
||||
tb.Helper()
|
||||
j1 = j1.Clone()
|
||||
j2 = j2.Clone()
|
||||
// Canonicalize JSON values for comparison.
|
||||
if err := j1.Canonicalize(); err != nil {
|
||||
tb.Error(err)
|
||||
}
|
||||
if err := j2.Canonicalize(); err != nil {
|
||||
tb.Error(err)
|
||||
}
|
||||
// Check and return true if the two values are structurally equal.
|
||||
if bytes.Equal(j1, j2) {
|
||||
return "", "", true
|
||||
}
|
||||
// Otherwise, format the values for display and return false.
|
||||
if err := j1.Indent("", "\t"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
if err := j2.Indent("", "\t"); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
return j1.String(), j2.String(), false
|
||||
}
|
||||
64
vendor/tailscale.com/util/syspolicy/internal/loggerx/logger.go
generated
vendored
Normal file
64
vendor/tailscale.com/util/syspolicy/internal/loggerx/logger.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package loggerx provides logging functions to the rest of the syspolicy packages.
|
||||
package loggerx
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
normalPrefix = "syspolicy: "
|
||||
verbosePrefix = "syspolicy: [v2] "
|
||||
)
|
||||
|
||||
var (
|
||||
debugLogging atomic.Bool // whether debugging logging is enabled
|
||||
|
||||
lazyPrintf lazy.SyncValue[logger.Logf]
|
||||
lazyVerbosef lazy.SyncValue[logger.Logf]
|
||||
)
|
||||
|
||||
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
|
||||
func SetDebugLoggingEnabled(v bool) {
|
||||
debugLogging.Store(v)
|
||||
}
|
||||
|
||||
// Errorf formats and writes an error message to the log.
|
||||
func Errorf(format string, args ...any) {
|
||||
printf(format, args...)
|
||||
}
|
||||
|
||||
// Verbosef formats and writes an optional, verbose message to the log.
|
||||
func Verbosef(format string, args ...any) {
|
||||
if debugLogging.Load() {
|
||||
printf(format, args...)
|
||||
} else {
|
||||
verbosef(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func printf(format string, args ...any) {
|
||||
lazyPrintf.Get(func() logger.Logf {
|
||||
return logger.WithPrefix(log.Printf, normalPrefix)
|
||||
})(format, args...)
|
||||
}
|
||||
|
||||
func verbosef(format string, args ...any) {
|
||||
lazyVerbosef.Get(func() logger.Logf {
|
||||
return logger.WithPrefix(log.Printf, verbosePrefix)
|
||||
})(format, args...)
|
||||
}
|
||||
|
||||
// SetForTest sets the specified printf and verbosef functions for the duration
|
||||
// of tb and its subtests.
|
||||
func SetForTest(tb internal.TB, printf, verbosef logger.Logf) {
|
||||
lazyPrintf.SetForTest(tb, printf, nil)
|
||||
lazyVerbosef.SetForTest(tb, verbosef, nil)
|
||||
}
|
||||
112
vendor/tailscale.com/util/syspolicy/policy_keys.go
generated
vendored
Normal file
112
vendor/tailscale.com/util/syspolicy/policy_keys.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import "tailscale.com/util/syspolicy/setting"
|
||||
|
||||
type Key = setting.Key
|
||||
|
||||
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"
|
||||
|
||||
// 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"
|
||||
|
||||
// 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"
|
||||
)
|
||||
38
vendor/tailscale.com/util/syspolicy/policy_keys_windows.go
generated
vendored
Normal file
38
vendor/tailscale.com/util/syspolicy/policy_keys_windows.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
var stringKeys = []Key{
|
||||
ControlURL,
|
||||
LogTarget,
|
||||
Tailnet,
|
||||
ExitNodeID,
|
||||
ExitNodeIP,
|
||||
EnableIncomingConnections,
|
||||
EnableServerMode,
|
||||
ExitNodeAllowLANAccess,
|
||||
EnableTailscaleDNS,
|
||||
EnableTailscaleSubnets,
|
||||
AdminConsoleVisibility,
|
||||
NetworkDevicesVisibility,
|
||||
TestMenuVisibility,
|
||||
UpdateMenuVisibility,
|
||||
RunExitNodeVisibility,
|
||||
PreferencesMenuVisibility,
|
||||
ExitNodeMenuVisibility,
|
||||
AutoUpdateVisibility,
|
||||
ResetToDefaultsVisibility,
|
||||
KeyExpirationNoticeTime,
|
||||
PostureChecking,
|
||||
ManagedByOrganizationName,
|
||||
ManagedByCaption,
|
||||
ManagedByURL,
|
||||
}
|
||||
|
||||
var boolKeys = []Key{
|
||||
LogSCMInteractions,
|
||||
FlushDNSOnSessionUnlock,
|
||||
}
|
||||
|
||||
var uint64Keys = []Key{}
|
||||
71
vendor/tailscale.com/util/syspolicy/setting/errors.go
generated
vendored
Normal file
71
vendor/tailscale.com/util/syspolicy/setting/errors.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotConfigured is returned when the requested policy setting is not configured.
|
||||
ErrNotConfigured = errors.New("not configured")
|
||||
// ErrTypeMismatch is returned when there's a type mismatch between the actual type
|
||||
// of the setting value and the expected type.
|
||||
ErrTypeMismatch = errors.New("type mismatch")
|
||||
// ErrNoSuchKey is returned by [DefinitionOf] when no policy setting
|
||||
// has been registered with the specified key.
|
||||
//
|
||||
// Until 2024-08-02, this error was also returned by a [Handler] when the specified
|
||||
// key did not have a value set. While the package maintains compatibility with this
|
||||
// usage of ErrNoSuchKey, it is recommended to return [ErrNotConfigured] from newer
|
||||
// [source.Store] implementations.
|
||||
ErrNoSuchKey = errors.New("no such key")
|
||||
)
|
||||
|
||||
// ErrorText represents an error that occurs when reading or parsing a policy setting.
|
||||
// This includes errors due to permissions issues, value type and format mismatches,
|
||||
// and other platform- or source-specific errors. It does not include
|
||||
// [ErrNotConfigured] and [ErrNoSuchKey], as those correspond to unconfigured
|
||||
// policy settings rather than settings that cannot be read or parsed
|
||||
// due to an error.
|
||||
//
|
||||
// ErrorText is used to marshal errors when a policy setting is sent over the wire,
|
||||
// allowing the error to be logged or displayed. It does not preserve the
|
||||
// type information of the underlying error.
|
||||
type ErrorText string
|
||||
|
||||
// NewErrorText returns a [ErrorText] with the specified error message.
|
||||
func NewErrorText(text string) *ErrorText {
|
||||
return ptr.To(ErrorText(text))
|
||||
}
|
||||
|
||||
// MaybeErrorText returns an [ErrorText] with the text of the specified error,
|
||||
// or nil if err is nil, [ErrNotConfigured], or [ErrNoSuchKey].
|
||||
func MaybeErrorText(err error) *ErrorText {
|
||||
if err == nil || errors.Is(err, ErrNotConfigured) || errors.Is(err, ErrNoSuchKey) {
|
||||
return nil
|
||||
}
|
||||
if err, ok := err.(*ErrorText); ok {
|
||||
return err
|
||||
}
|
||||
return ptr.To(ErrorText(err.Error()))
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e ErrorText) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (e ErrorText) MarshalText() (text []byte, err error) {
|
||||
return []byte(e.Error()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
func (e *ErrorText) UnmarshalText(text []byte) error {
|
||||
*e = ErrorText(text)
|
||||
return nil
|
||||
}
|
||||
13
vendor/tailscale.com/util/syspolicy/setting/key.go
generated
vendored
Normal file
13
vendor/tailscale.com/util/syspolicy/setting/key.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// 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 = "/"
|
||||
71
vendor/tailscale.com/util/syspolicy/setting/origin.go
generated
vendored
Normal file
71
vendor/tailscale.com/util/syspolicy/setting/origin.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
// Origin describes where a policy or a policy setting is configured.
|
||||
type Origin struct {
|
||||
data settingOrigin
|
||||
}
|
||||
|
||||
// settingOrigin is the marshallable data of an [Origin].
|
||||
type settingOrigin struct {
|
||||
Name string `json:",omitzero"`
|
||||
Scope PolicyScope
|
||||
}
|
||||
|
||||
// NewOrigin returns a new [Origin] with the specified scope.
|
||||
func NewOrigin(scope PolicyScope) *Origin {
|
||||
return NewNamedOrigin("", scope)
|
||||
}
|
||||
|
||||
// NewNamedOrigin returns a new [Origin] with the specified scope and name.
|
||||
func NewNamedOrigin(name string, scope PolicyScope) *Origin {
|
||||
return &Origin{settingOrigin{name, scope}}
|
||||
}
|
||||
|
||||
// Scope reports the policy [PolicyScope] where the setting is configured.
|
||||
func (s Origin) Scope() PolicyScope {
|
||||
return s.data.Scope
|
||||
}
|
||||
|
||||
// Name returns the name of the policy source where the setting is configured,
|
||||
// or "" if not available.
|
||||
func (s Origin) Name() string {
|
||||
return s.data.Name
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Origin) String() string {
|
||||
if s.Name() != "" {
|
||||
return fmt.Sprintf("%s (%v)", s.Name(), s.Scope())
|
||||
}
|
||||
return s.Scope().String()
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (s Origin) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(out, &s.data, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (s *Origin) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
return jsonv2.UnmarshalDecode(in, &s.data, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (s Origin) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(s) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (s *Origin) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
|
||||
}
|
||||
189
vendor/tailscale.com/util/syspolicy/setting/policy_scope.go
generated
vendored
Normal file
189
vendor/tailscale.com/util/syspolicy/setting/policy_scope.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
)
|
||||
|
||||
var (
|
||||
lazyDefaultScope lazy.SyncValue[PolicyScope]
|
||||
|
||||
// DeviceScope indicates a scope containing device-global policies.
|
||||
DeviceScope = PolicyScope{kind: DeviceSetting}
|
||||
// CurrentProfileScope indicates a scope containing policies that apply to the
|
||||
// currently active Tailscale profile.
|
||||
CurrentProfileScope = PolicyScope{kind: ProfileSetting}
|
||||
// CurrentUserScope indicates a scope containing policies that apply to the
|
||||
// current user, for whatever that means on the current platform and
|
||||
// in the current application context.
|
||||
CurrentUserScope = PolicyScope{kind: UserSetting}
|
||||
)
|
||||
|
||||
// PolicyScope is a management scope.
|
||||
type PolicyScope struct {
|
||||
kind Scope
|
||||
userID string
|
||||
profileID string
|
||||
}
|
||||
|
||||
// DefaultScope returns the default [PolicyScope] to be used by a program
|
||||
// when querying policy settings.
|
||||
// It returns [DeviceScope], unless explicitly changed with [SetDefaultScope].
|
||||
func DefaultScope() PolicyScope {
|
||||
return lazyDefaultScope.Get(func() PolicyScope { return DeviceScope })
|
||||
}
|
||||
|
||||
// SetDefaultScope attempts to set the specified scope as the default scope
|
||||
// to be used by a program when querying policy settings.
|
||||
// It fails and returns false if called more than once, or if the [DefaultScope]
|
||||
// has already been used.
|
||||
func SetDefaultScope(scope PolicyScope) bool {
|
||||
return lazyDefaultScope.Set(scope)
|
||||
}
|
||||
|
||||
// UserScopeOf returns a policy [PolicyScope] of the user with the specified id.
|
||||
func UserScopeOf(uid string) PolicyScope {
|
||||
return PolicyScope{kind: UserSetting, userID: uid}
|
||||
}
|
||||
|
||||
// Kind reports the scope kind of s.
|
||||
func (s PolicyScope) Kind() Scope {
|
||||
return s.kind
|
||||
}
|
||||
|
||||
// IsApplicableSetting reports whether the specified setting applies to
|
||||
// and can be retrieved for this scope. Policy settings are applicable
|
||||
// to their own scopes as well as more specific scopes. For example,
|
||||
// device settings are applicable to device, profile and user scopes,
|
||||
// but user settings are only applicable to user scopes.
|
||||
// For instance, a menu visibility setting is inherently a user setting
|
||||
// and only makes sense in the context of a specific user.
|
||||
func (s PolicyScope) IsApplicableSetting(setting *Definition) bool {
|
||||
return setting != nil && setting.Scope() <= s.Kind()
|
||||
}
|
||||
|
||||
// IsConfigurableSetting reports whether the specified setting can be configured
|
||||
// by a policy at this scope. Policy settings are configurable at their own scopes
|
||||
// as well as broader scopes. For example, [UserSetting]s are configurable in
|
||||
// user, profile, and device scopes, but [DeviceSetting]s are only configurable
|
||||
// in the [DeviceScope]. For instance, the InstallUpdates policy setting
|
||||
// can only be configured in the device scope, as it controls whether updates
|
||||
// will be installed automatically on the device, rather than for specific users.
|
||||
func (s PolicyScope) IsConfigurableSetting(setting *Definition) bool {
|
||||
return setting != nil && setting.Scope() >= s.Kind()
|
||||
}
|
||||
|
||||
// Contains reports whether policy settings that apply to s also apply to s2.
|
||||
// For example, policy settings that apply to the [DeviceScope] also apply to
|
||||
// the [CurrentUserScope].
|
||||
func (s PolicyScope) Contains(s2 PolicyScope) bool {
|
||||
if s.Kind() > s2.Kind() {
|
||||
return false
|
||||
}
|
||||
switch s.Kind() {
|
||||
case DeviceSetting:
|
||||
return true
|
||||
case ProfileSetting:
|
||||
return s.profileID == s2.profileID
|
||||
case UserSetting:
|
||||
return s.userID == s2.userID
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// StrictlyContains is like [PolicyScope.Contains], but returns false
|
||||
// when s and s2 is the same scope.
|
||||
func (s PolicyScope) StrictlyContains(s2 PolicyScope) bool {
|
||||
return s != s2 && s.Contains(s2)
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s PolicyScope) String() string {
|
||||
if s.profileID == "" && s.userID == "" {
|
||||
return s.kind.String()
|
||||
}
|
||||
return s.stringSlow()
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (s PolicyScope) MarshalText() ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextUnmarshaler].
|
||||
func (s *PolicyScope) UnmarshalText(b []byte) error {
|
||||
*s = PolicyScope{}
|
||||
parts := strings.SplitN(string(b), "/", 2)
|
||||
for i, part := range parts {
|
||||
kind, id, err := parseScopeAndID(part)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i > 0 && kind <= s.kind {
|
||||
return fmt.Errorf("invalid scope hierarchy: %s", b)
|
||||
}
|
||||
s.kind = kind
|
||||
switch kind {
|
||||
case DeviceSetting:
|
||||
if id != "" {
|
||||
return fmt.Errorf("the device scope must not have an ID: %s", b)
|
||||
}
|
||||
case ProfileSetting:
|
||||
s.profileID = id
|
||||
case UserSetting:
|
||||
s.userID = id
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s PolicyScope) stringSlow() string {
|
||||
var sb strings.Builder
|
||||
writeScopeWithID := func(s Scope, id string) {
|
||||
sb.WriteString(s.String())
|
||||
if id != "" {
|
||||
sb.WriteRune('(')
|
||||
sb.WriteString(id)
|
||||
sb.WriteRune(')')
|
||||
}
|
||||
}
|
||||
if s.kind == ProfileSetting || s.profileID != "" {
|
||||
writeScopeWithID(ProfileSetting, s.profileID)
|
||||
if s.kind != ProfileSetting {
|
||||
sb.WriteRune('/')
|
||||
}
|
||||
}
|
||||
if s.kind == UserSetting {
|
||||
writeScopeWithID(UserSetting, s.userID)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func parseScopeAndID(s string) (scope Scope, id string, err error) {
|
||||
name, params, ok := extractScopeAndParams(s)
|
||||
if !ok {
|
||||
return 0, "", fmt.Errorf("%q is not a valid scope string", s)
|
||||
}
|
||||
if err := scope.UnmarshalText([]byte(name)); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return scope, params, nil
|
||||
}
|
||||
|
||||
func extractScopeAndParams(s string) (name, params string, ok bool) {
|
||||
paramsStart := strings.Index(s, "(")
|
||||
if paramsStart == -1 {
|
||||
return s, "", true
|
||||
}
|
||||
paramsEnd := strings.LastIndex(s, ")")
|
||||
if paramsEnd < paramsStart {
|
||||
return "", "", false
|
||||
}
|
||||
return s[0:paramsStart], s[paramsStart+1 : paramsEnd], true
|
||||
}
|
||||
67
vendor/tailscale.com/util/syspolicy/setting/raw_item.go
generated
vendored
Normal file
67
vendor/tailscale.com/util/syspolicy/setting/raw_item.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tailscale.com/types/structs"
|
||||
)
|
||||
|
||||
// RawItem contains a raw policy setting value as read from a policy store, or an
|
||||
// error if the requested setting could not be read from the store. As a special
|
||||
// case, it may also hold a value of the [Visibility], [PreferenceOption],
|
||||
// or [time.Duration] types. While the policy store interface does not support
|
||||
// these types natively, and the values of these types have to be unmarshalled
|
||||
// or converted from strings, these setting types predate the typed policy
|
||||
// hierarchies, and must be supported at this layer.
|
||||
type RawItem struct {
|
||||
_ structs.Incomparable
|
||||
value any
|
||||
err *ErrorText
|
||||
origin *Origin // or nil
|
||||
}
|
||||
|
||||
// RawItemOf returns a [RawItem] with the specified value.
|
||||
func RawItemOf(value any) RawItem {
|
||||
return RawItemWith(value, nil, nil)
|
||||
}
|
||||
|
||||
// RawItemWith returns a [RawItem] with the specified value, error and origin.
|
||||
func RawItemWith(value any, err *ErrorText, origin *Origin) RawItem {
|
||||
return RawItem{value: value, err: err, origin: origin}
|
||||
}
|
||||
|
||||
// Value returns the value of the policy setting, or nil if the policy setting
|
||||
// is not configured, or an error occurred while reading it.
|
||||
func (i RawItem) Value() any {
|
||||
return i.value
|
||||
}
|
||||
|
||||
// Error returns the error that occurred when reading the policy setting,
|
||||
// or nil if no error occurred.
|
||||
func (i RawItem) Error() error {
|
||||
if i.err != nil {
|
||||
return i.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Origin returns an optional [Origin] indicating where the policy setting is
|
||||
// configured.
|
||||
func (i RawItem) Origin() *Origin {
|
||||
return i.origin
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (i RawItem) String() string {
|
||||
var suffix string
|
||||
if i.origin != nil {
|
||||
suffix = fmt.Sprintf(" - {%v}", i.origin)
|
||||
}
|
||||
if i.err != nil {
|
||||
return fmt.Sprintf("Error{%q}%s", i.err.Error(), suffix)
|
||||
}
|
||||
return fmt.Sprintf("%v%s", i.value, suffix)
|
||||
}
|
||||
348
vendor/tailscale.com/util/syspolicy/setting/setting.go
generated
vendored
Normal file
348
vendor/tailscale.com/util/syspolicy/setting/setting.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package setting contains types for defining and representing policy settings.
|
||||
// It facilitates the registration of setting definitions using [Register] and [RegisterDefinition],
|
||||
// and the retrieval of registered setting definitions via [Definitions] and [DefinitionOf].
|
||||
// This package is intended for use primarily within the syspolicy package hierarchy.
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
)
|
||||
|
||||
// Scope indicates the broadest scope at which a policy setting may apply,
|
||||
// and the narrowest scope at which it may be configured.
|
||||
type Scope int8
|
||||
|
||||
const (
|
||||
// DeviceSetting indicates a policy setting that applies to a device, regardless of
|
||||
// which OS user or Tailscale profile is currently active, if any.
|
||||
// It can only be configured at a [DeviceScope].
|
||||
DeviceSetting Scope = iota
|
||||
// ProfileSetting indicates a policy setting that applies to a Tailscale profile.
|
||||
// It can only be configured for a specific profile or at a [DeviceScope],
|
||||
// in which case it applies to all profiles on the device.
|
||||
ProfileSetting
|
||||
// UserSetting indicates a policy setting that applies to users.
|
||||
// It can be configured for a user, profile, or the entire device.
|
||||
UserSetting
|
||||
|
||||
// NumScopes is the number of possible [Scope] values.
|
||||
NumScopes int = iota // must be the last value in the const block.
|
||||
)
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Scope) String() string {
|
||||
switch s {
|
||||
case DeviceSetting:
|
||||
return "Device"
|
||||
case ProfileSetting:
|
||||
return "Profile"
|
||||
case UserSetting:
|
||||
return "User"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (s Scope) MarshalText() (text []byte, err error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
func (s *Scope) UnmarshalText(text []byte) error {
|
||||
switch strings.ToLower(string(text)) {
|
||||
case "device":
|
||||
*s = DeviceSetting
|
||||
case "profile":
|
||||
*s = ProfileSetting
|
||||
case "user":
|
||||
*s = UserSetting
|
||||
default:
|
||||
return fmt.Errorf("%q is not a valid scope", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type is a policy setting value type.
|
||||
// Except for [InvalidValue], which represents an invalid policy setting type,
|
||||
// and [PreferenceOptionValue], [VisibilityValue], and [DurationValue],
|
||||
// which have special handling due to their legacy status in the package,
|
||||
// SettingTypes represent the raw value types readable from policy stores.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// InvalidValue indicates an invalid policy setting value type.
|
||||
InvalidValue Type = iota
|
||||
// BooleanValue indicates a policy setting whose underlying type is a bool.
|
||||
BooleanValue
|
||||
// IntegerValue indicates a policy setting whose underlying type is a uint64.
|
||||
IntegerValue
|
||||
// StringValue indicates a policy setting whose underlying type is a string.
|
||||
StringValue
|
||||
// StringListValue indicates a policy setting whose underlying type is a []string.
|
||||
StringListValue
|
||||
// PreferenceOptionValue indicates a three-state policy setting whose
|
||||
// underlying type is a string, but the actual value is a [PreferenceOption].
|
||||
PreferenceOptionValue
|
||||
// VisibilityValue indicates a two-state boolean-like policy setting whose
|
||||
// underlying type is a string, but the actual value is a [Visibility].
|
||||
VisibilityValue
|
||||
// DurationValue indicates an interval/period/duration policy setting whose
|
||||
// underlying type is a string, but the actual value is a [time.Duration].
|
||||
DurationValue
|
||||
)
|
||||
|
||||
// String returns a string representation of t.
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case InvalidValue:
|
||||
return "Invalid"
|
||||
case BooleanValue:
|
||||
return "Boolean"
|
||||
case IntegerValue:
|
||||
return "Integer"
|
||||
case StringValue:
|
||||
return "String"
|
||||
case StringListValue:
|
||||
return "StringList"
|
||||
case PreferenceOptionValue:
|
||||
return "PreferenceOption"
|
||||
case VisibilityValue:
|
||||
return "Visibility"
|
||||
case DurationValue:
|
||||
return "Duration"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// ValueType is a constraint that allows Go types corresponding to [Type].
|
||||
type ValueType interface {
|
||||
bool | uint64 | string | []string | Visibility | PreferenceOption | time.Duration
|
||||
}
|
||||
|
||||
// Definition defines policy key, scope and value type.
|
||||
type Definition struct {
|
||||
key Key
|
||||
scope Scope
|
||||
typ Type
|
||||
platforms PlatformList
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &Definition{key: k, scope: s, typ: t, platforms: platforms}
|
||||
}
|
||||
|
||||
// Key returns a policy setting's identifier.
|
||||
func (d *Definition) Key() Key {
|
||||
if d == nil {
|
||||
return ""
|
||||
}
|
||||
return d.key
|
||||
}
|
||||
|
||||
// Scope reports the broadest [Scope] the policy setting may apply to.
|
||||
func (d *Definition) Scope() Scope {
|
||||
if d == nil {
|
||||
return 0
|
||||
}
|
||||
return d.scope
|
||||
}
|
||||
|
||||
// Type reports the underlying value type of the policy setting.
|
||||
func (d *Definition) Type() Type {
|
||||
if d == nil {
|
||||
return InvalidValue
|
||||
}
|
||||
return d.typ
|
||||
}
|
||||
|
||||
// IsSupported reports whether the policy setting is supported on the current OS.
|
||||
func (d *Definition) IsSupported() bool {
|
||||
if d == nil {
|
||||
return false
|
||||
}
|
||||
return d.platforms.HasCurrent()
|
||||
}
|
||||
|
||||
// SupportedPlatforms reports platforms on which the policy setting is supported.
|
||||
// An empty [PlatformList] indicates that s is available on all platforms.
|
||||
func (d *Definition) SupportedPlatforms() PlatformList {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return d.platforms
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (d *Definition) String() string {
|
||||
if d == nil {
|
||||
return "(nil)"
|
||||
}
|
||||
return fmt.Sprintf("%v(%q, %v)", d.scope, d.key, d.typ)
|
||||
}
|
||||
|
||||
// Equal reports whether d and d2 have the same key, type and scope.
|
||||
// It does not check whether both s and s2 are supported on the same platforms.
|
||||
func (d *Definition) Equal(d2 *Definition) bool {
|
||||
if d == d2 {
|
||||
return true
|
||||
}
|
||||
if d == nil || d2 == nil {
|
||||
return false
|
||||
}
|
||||
return d.key == d2.key && d.typ == d2.typ && d.scope == d2.scope
|
||||
}
|
||||
|
||||
// DefinitionMap is a map of setting [Definition] by [Key].
|
||||
type DefinitionMap map[Key]*Definition
|
||||
|
||||
var (
|
||||
definitions lazy.SyncValue[DefinitionMap]
|
||||
|
||||
definitionsMu sync.Mutex
|
||||
definitionsList []*Definition
|
||||
definitionsUsed bool
|
||||
)
|
||||
|
||||
// Register registers a policy setting with the specified key, scope, value type,
|
||||
// and an optional list of supported platforms. All policy settings must be
|
||||
// registered before any of them can be used. Register panics if called after
|
||||
// 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) {
|
||||
RegisterDefinition(NewDefinition(k, s, t, platforms...))
|
||||
}
|
||||
|
||||
// RegisterDefinition is like [Register], but accepts a [Definition].
|
||||
func RegisterDefinition(d *Definition) {
|
||||
definitionsMu.Lock()
|
||||
defer definitionsMu.Unlock()
|
||||
registerLocked(d)
|
||||
}
|
||||
|
||||
func registerLocked(d *Definition) {
|
||||
if definitionsUsed {
|
||||
panic("policy definitions are already in use")
|
||||
}
|
||||
definitionsList = append(definitionsList, d)
|
||||
}
|
||||
|
||||
func settingDefinitions() (DefinitionMap, error) {
|
||||
return definitions.GetErr(func() (DefinitionMap, error) {
|
||||
definitionsMu.Lock()
|
||||
defer definitionsMu.Unlock()
|
||||
definitionsUsed = true
|
||||
return DefinitionMapOf(definitionsList)
|
||||
})
|
||||
}
|
||||
|
||||
// DefinitionMapOf returns a [DefinitionMap] with the specified settings,
|
||||
// or an error if any settings have the same key but different type or scope.
|
||||
func DefinitionMapOf(settings []*Definition) (DefinitionMap, error) {
|
||||
m := make(DefinitionMap, len(settings))
|
||||
for _, s := range settings {
|
||||
if existing, exists := m[s.key]; exists {
|
||||
if existing.Equal(s) {
|
||||
// Ignore duplicate setting definitions if they match. It is acceptable
|
||||
// if the same policy setting was registered more than once
|
||||
// (e.g. by the syspolicy package itself and by iOS/Android code).
|
||||
existing.platforms.mergeFrom(s.platforms)
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("duplicate policy definition: %q", s.key)
|
||||
}
|
||||
m[s.key] = s
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetDefinitionsForTest allows to register the specified setting definitions
|
||||
// for the test duration. It is not concurrency-safe, but unlike [Register],
|
||||
// it does not panic and can be called anytime.
|
||||
// It returns an error if ds contains two different settings with the same [Key].
|
||||
func SetDefinitionsForTest(tb lazy.TB, ds ...*Definition) error {
|
||||
m, err := DefinitionMapOf(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
definitions.SetForTest(tb, m, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
ds, err := settingDefinitions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d, ok := ds[k]; ok {
|
||||
return d, nil
|
||||
}
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
|
||||
// Definitions returns all registered setting definitions,
|
||||
// or an error if different policies were registered under the same name.
|
||||
func Definitions() ([]*Definition, error) {
|
||||
ds, err := settingDefinitions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]*Definition, 0, len(ds))
|
||||
for _, d := range ds {
|
||||
res = append(res, d)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// PlatformList is a list of OSes.
|
||||
// An empty list indicates that all possible platforms are supported.
|
||||
type PlatformList []string
|
||||
|
||||
// Has reports whether l contains the target platform.
|
||||
func (l PlatformList) Has(target string) bool {
|
||||
if len(l) == 0 {
|
||||
return true
|
||||
}
|
||||
return slices.ContainsFunc(l, func(os string) bool {
|
||||
return strings.EqualFold(os, target)
|
||||
})
|
||||
}
|
||||
|
||||
// HasCurrent is like Has, but for the current platform.
|
||||
func (l PlatformList) HasCurrent() bool {
|
||||
return l.Has(internal.OS())
|
||||
}
|
||||
|
||||
// mergeFrom merges l2 into l. Since an empty list indicates no platform restrictions,
|
||||
// if either l or l2 is empty, the merged result in l will also be empty.
|
||||
func (l *PlatformList) mergeFrom(l2 PlatformList) {
|
||||
switch {
|
||||
case len(*l) == 0:
|
||||
// No-op. An empty list indicates no platform restrictions.
|
||||
case len(l2) == 0:
|
||||
// Merging with an empty list results in an empty list.
|
||||
*l = l2
|
||||
default:
|
||||
// Append, sort and dedup.
|
||||
*l = append(*l, l2...)
|
||||
slices.Sort(*l)
|
||||
*l = slices.Compact(*l)
|
||||
}
|
||||
}
|
||||
170
vendor/tailscale.com/util/syspolicy/setting/snapshot.go
generated
vendored
Normal file
170
vendor/tailscale.com/util/syspolicy/setting/snapshot.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/util/deephash"
|
||||
)
|
||||
|
||||
// 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
|
||||
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 {
|
||||
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] {
|
||||
if s == nil {
|
||||
return func(yield func(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 {
|
||||
v, _ := s.GetErr(k)
|
||||
return v
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if s != nil {
|
||||
if s, ok := s.m[k]; ok {
|
||||
return s.Value(), s.Error()
|
||||
}
|
||||
}
|
||||
return nil, ErrNotConfigured
|
||||
}
|
||||
|
||||
// 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) {
|
||||
setting, ok = s.m[k]
|
||||
return setting, ok
|
||||
}
|
||||
|
||||
// Equal reports whether s and s2 are equal.
|
||||
func (s *Snapshot) Equal(s2 *Snapshot) bool {
|
||||
if !s.EqualItems(s2) {
|
||||
return false
|
||||
}
|
||||
return s.Summary() == s2.Summary()
|
||||
}
|
||||
|
||||
// EqualItems reports whether items in s and s2 are equal.
|
||||
func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
|
||||
if s == s2 {
|
||||
return true
|
||||
}
|
||||
if s.Len() != s2.Len() {
|
||||
return false
|
||||
}
|
||||
if s.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
return s.sig == s2.sig
|
||||
}
|
||||
|
||||
// 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] {
|
||||
if s.m == nil {
|
||||
return func(yield func(Key) bool) {}
|
||||
}
|
||||
return maps.Keys(s.m)
|
||||
}
|
||||
|
||||
// Len reports the number of [RawItem]s in s.
|
||||
func (s *Snapshot) Len() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return len(s.m)
|
||||
}
|
||||
|
||||
// Summary returns information about s as a whole rather than about specific [RawItem]s in it.
|
||||
func (s *Snapshot) Summary() Summary {
|
||||
if s == nil {
|
||||
return Summary{}
|
||||
}
|
||||
return s.summary
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer]
|
||||
func (s *Snapshot) String() string {
|
||||
if s.Len() == 0 && s.Summary().IsEmpty() {
|
||||
return "{Empty}"
|
||||
}
|
||||
var sb strings.Builder
|
||||
if !s.summary.IsEmpty() {
|
||||
sb.WriteRune('{')
|
||||
if s.Len() == 0 {
|
||||
sb.WriteString("Empty, ")
|
||||
}
|
||||
sb.WriteString(s.summary.String())
|
||||
sb.WriteRune('}')
|
||||
}
|
||||
for _, k := range slices.Sorted(s.Keys()) {
|
||||
if sb.Len() != 0 {
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
sb.WriteString(string(k))
|
||||
sb.WriteString(" = ")
|
||||
sb.WriteString(s.m[k].String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// MergeSnapshots returns a [Snapshot] that contains all [RawItem]s
|
||||
// from snapshot1 and snapshot2 and the [Summary] with the narrower [PolicyScope].
|
||||
// If there's a conflict between policy settings in the two snapshots,
|
||||
// the policy settings from the snapshot with the broader scope take precedence.
|
||||
// In other words, policy settings configured for the [DeviceScope] win
|
||||
// over policy settings configured for a user scope.
|
||||
func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
|
||||
scope1, ok1 := snapshot1.Summary().Scope().GetOk()
|
||||
scope2, ok2 := snapshot2.Summary().Scope().GetOk()
|
||||
if ok1 && ok2 && scope1.StrictlyContains(scope2) {
|
||||
// Swap snapshots if snapshot1 has higher precedence than snapshot2.
|
||||
snapshot1, snapshot2 = snapshot2, snapshot1
|
||||
}
|
||||
if snapshot2.Len() == 0 {
|
||||
return snapshot1
|
||||
}
|
||||
summaryOpts := make([]SummaryOption, 0, 2)
|
||||
if scope, ok := snapshot1.Summary().Scope().GetOk(); ok {
|
||||
// Use the scope from snapshot1, if present, which is the more specific snapshot.
|
||||
summaryOpts = append(summaryOpts, scope)
|
||||
}
|
||||
if snapshot1.Len() == 0 {
|
||||
if origin, ok := snapshot2.Summary().Origin().GetOk(); ok {
|
||||
// Use the origin from snapshot2 if snapshot1 is empty.
|
||||
summaryOpts = append(summaryOpts, origin)
|
||||
}
|
||||
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
|
||||
}
|
||||
m := make(map[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...)}
|
||||
}
|
||||
100
vendor/tailscale.com/util/syspolicy/setting/summary.go
generated
vendored
Normal file
100
vendor/tailscale.com/util/syspolicy/setting/summary.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"tailscale.com/types/opt"
|
||||
)
|
||||
|
||||
// Summary is an immutable [PolicyScope] and [Origin].
|
||||
type Summary struct {
|
||||
data summary
|
||||
}
|
||||
|
||||
type summary struct {
|
||||
Scope opt.Value[PolicyScope] `json:",omitzero"`
|
||||
Origin opt.Value[Origin] `json:",omitzero"`
|
||||
}
|
||||
|
||||
// SummaryWith returns a [Summary] with the specified options.
|
||||
func SummaryWith(opts ...SummaryOption) Summary {
|
||||
var summary Summary
|
||||
for _, o := range opts {
|
||||
o.applySummaryOption(&summary)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
// IsEmpty reports whether s is empty.
|
||||
func (s Summary) IsEmpty() bool {
|
||||
return s == Summary{}
|
||||
}
|
||||
|
||||
// Scope reports the [PolicyScope] in s.
|
||||
func (s Summary) Scope() opt.Value[PolicyScope] {
|
||||
return s.data.Scope
|
||||
}
|
||||
|
||||
// Origin reports the [Origin] in s.
|
||||
func (s Summary) Origin() opt.Value[Origin] {
|
||||
return s.data.Origin
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (s Summary) String() string {
|
||||
if s.IsEmpty() {
|
||||
return "{Empty}"
|
||||
}
|
||||
if origin, ok := s.data.Origin.GetOk(); ok {
|
||||
return origin.String()
|
||||
}
|
||||
return s.data.Scope.String()
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (s Summary) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
return jsonv2.MarshalEncode(out, &s.data, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (s *Summary) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
return jsonv2.UnmarshalDecode(in, &s.data, opts)
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (s Summary) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(s) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (s *Summary) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
|
||||
}
|
||||
|
||||
// SummaryOption is an option that configures [Summary]
|
||||
// The following are allowed options:
|
||||
//
|
||||
// - [Summary]
|
||||
// - [PolicyScope]
|
||||
// - [Origin]
|
||||
type SummaryOption interface {
|
||||
applySummaryOption(summary *Summary)
|
||||
}
|
||||
|
||||
func (s PolicyScope) applySummaryOption(summary *Summary) {
|
||||
summary.data.Scope.Set(s)
|
||||
}
|
||||
|
||||
func (o Origin) applySummaryOption(summary *Summary) {
|
||||
summary.data.Origin.Set(o)
|
||||
if !summary.data.Scope.IsSet() {
|
||||
summary.data.Scope.Set(o.Scope())
|
||||
}
|
||||
}
|
||||
|
||||
func (s Summary) applySummaryOption(summary *Summary) {
|
||||
*summary = s
|
||||
}
|
||||
136
vendor/tailscale.com/util/syspolicy/setting/types.go
generated
vendored
Normal file
136
vendor/tailscale.com/util/syspolicy/setting/types.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// PreferenceOption is a policy that governs whether a boolean variable
|
||||
// is forcibly assigned an administrator-defined value, or allowed to receive
|
||||
// a user-defined value.
|
||||
type PreferenceOption byte
|
||||
|
||||
const (
|
||||
ShowChoiceByPolicy PreferenceOption = iota
|
||||
NeverByPolicy
|
||||
AlwaysByPolicy
|
||||
)
|
||||
|
||||
// Show returns if the UI option that controls the choice administered by this
|
||||
// policy should be shown. Currently this is true if and only if the policy is
|
||||
// [ShowChoiceByPolicy].
|
||||
func (p PreferenceOption) Show() bool {
|
||||
return p == ShowChoiceByPolicy
|
||||
}
|
||||
|
||||
// ShouldEnable checks if the choice administered by this policy should be
|
||||
// enabled. If the administrator has chosen a setting, the administrator's
|
||||
// setting is returned, otherwise userChoice is returned.
|
||||
func (p PreferenceOption) ShouldEnable(userChoice bool) bool {
|
||||
switch p {
|
||||
case NeverByPolicy:
|
||||
return false
|
||||
case AlwaysByPolicy:
|
||||
return true
|
||||
default:
|
||||
return userChoice
|
||||
}
|
||||
}
|
||||
|
||||
// IsAlways reports whether the preference should always be enabled.
|
||||
func (p PreferenceOption) IsAlways() bool {
|
||||
return p == AlwaysByPolicy
|
||||
}
|
||||
|
||||
// IsNever reports whether the preference should always be disabled.
|
||||
func (p PreferenceOption) IsNever() bool {
|
||||
return p == NeverByPolicy
|
||||
}
|
||||
|
||||
// WillOverride checks if the choice administered by the policy is different
|
||||
// from the user's choice.
|
||||
func (p PreferenceOption) WillOverride(userChoice bool) bool {
|
||||
return p.ShouldEnable(userChoice) != userChoice
|
||||
}
|
||||
|
||||
// String returns a string representation of p.
|
||||
func (p PreferenceOption) String() string {
|
||||
switch p {
|
||||
case AlwaysByPolicy:
|
||||
return "always"
|
||||
case NeverByPolicy:
|
||||
return "never"
|
||||
default:
|
||||
return "user-decides"
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (p *PreferenceOption) MarshalText() (text []byte, err error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It never fails and sets p to [ShowChoiceByPolicy] if the specified text
|
||||
// does not represent a valid [PreferenceOption].
|
||||
func (p *PreferenceOption) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "always":
|
||||
*p = AlwaysByPolicy
|
||||
case "never":
|
||||
*p = NeverByPolicy
|
||||
default:
|
||||
*p = ShowChoiceByPolicy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visibility is a policy that controls whether or not a particular
|
||||
// component of a user interface is to be shown.
|
||||
type Visibility byte
|
||||
|
||||
var (
|
||||
_ encoding.TextMarshaler = (*Visibility)(nil)
|
||||
_ encoding.TextUnmarshaler = (*Visibility)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
VisibleByPolicy Visibility = 'v'
|
||||
HiddenByPolicy Visibility = 'h'
|
||||
)
|
||||
|
||||
// Show reports whether the UI option administered by this policy should be shown.
|
||||
// Currently this is true if the policy is not [hiddenByPolicy].
|
||||
func (v Visibility) Show() bool {
|
||||
return v != HiddenByPolicy
|
||||
}
|
||||
|
||||
// String returns a string representation of v.
|
||||
func (v Visibility) String() string {
|
||||
switch v {
|
||||
case 'h':
|
||||
return "hide"
|
||||
default:
|
||||
return "show"
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler].
|
||||
func (v Visibility) MarshalText() (text []byte, err error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It never fails and sets v to [VisibleByPolicy] if the specified text
|
||||
// does not represent a valid [Visibility].
|
||||
func (v *Visibility) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case "hide":
|
||||
*v = HiddenByPolicy
|
||||
default:
|
||||
*v = VisibleByPolicy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
143
vendor/tailscale.com/util/syspolicy/syspolicy.go
generated
vendored
Normal file
143
vendor/tailscale.com/util/syspolicy/syspolicy.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package syspolicy provides functions to retrieve system settings of a device.
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
func GetString(key Key, defaultValue string) (string, error) {
|
||||
markHandlerInUse()
|
||||
v, err := handler.ReadString(string(key))
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func GetUint64(key Key, defaultValue uint64) (uint64, error) {
|
||||
markHandlerInUse()
|
||||
v, err := handler.ReadUInt64(string(key))
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func GetBoolean(key Key, defaultValue bool) (bool, error) {
|
||||
markHandlerInUse()
|
||||
v, err := handler.ReadBoolean(string(key))
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func GetStringArray(key Key, defaultValue []string) ([]string, error) {
|
||||
markHandlerInUse()
|
||||
v, err := handler.ReadStringArray(string(key))
|
||||
if errors.Is(err, ErrNoSuchKey) {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetPreferenceOption loads a policy from the registry that can be
|
||||
// managed by an enterprise policy management system and allows administrative
|
||||
// overrides of users' choices in a way that we do not want tailcontrol to have
|
||||
// the authority to set. It describes user-decides/always/never options, where
|
||||
// "always" and "never" remove the user's ability to make a selection. If not
|
||||
// present or set to a different value, "user-decides" is the default.
|
||||
func GetPreferenceOption(name Key) (setting.PreferenceOption, error) {
|
||||
s, err := GetString(name, "user-decides")
|
||||
if err != nil {
|
||||
return setting.ShowChoiceByPolicy, err
|
||||
}
|
||||
var opt setting.PreferenceOption
|
||||
err = opt.UnmarshalText([]byte(s))
|
||||
return opt, err
|
||||
}
|
||||
|
||||
// GetVisibility loads a policy from the registry that can be managed
|
||||
// by an enterprise policy management system and describes show/hide decisions
|
||||
// for UI elements. The registry value should be a string set to "show" (return
|
||||
// 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) {
|
||||
s, err := GetString(name, "show")
|
||||
if err != nil {
|
||||
return setting.VisibleByPolicy, err
|
||||
}
|
||||
var visibility setting.Visibility
|
||||
visibility.UnmarshalText([]byte(s))
|
||||
return visibility, nil
|
||||
}
|
||||
|
||||
// GetDuration loads a policy from the registry that can be managed
|
||||
// by an enterprise policy management system and describes a duration for some
|
||||
// action. The registry value should be a string that time.ParseDuration
|
||||
// understands. If the registry value is "" or can not be processed,
|
||||
// defaultValue is returned instead.
|
||||
func GetDuration(name Key, defaultValue time.Duration) (time.Duration, error) {
|
||||
opt, err := GetString(name, "")
|
||||
if opt == "" || err != nil {
|
||||
return defaultValue, err
|
||||
}
|
||||
v, err := time.ParseDuration(opt)
|
||||
if err != nil || v < 0 {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// SelectControlURL returns the ControlURL to use based on a value in
|
||||
// the registry (LoginURL) and the one on disk (in the GUI's
|
||||
// prefs.conf). If both are empty, it returns a default value. (It
|
||||
// always return a non-empty value)
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/2798 for some background.
|
||||
func SelectControlURL(reg, disk string) string {
|
||||
const def = "https://controlplane.tailscale.com"
|
||||
|
||||
// Prior to Dec 2020's commit 739b02e6, the installer
|
||||
// wrote a LoginURL value of https://login.tailscale.com to the registry.
|
||||
const oldRegDef = "https://login.tailscale.com"
|
||||
|
||||
// If they have an explicit value in the registry, use it,
|
||||
// unless it's an old default value from an old installer.
|
||||
// Then we have to see which is better.
|
||||
if reg != "" {
|
||||
if reg != oldRegDef {
|
||||
// Something explicit in the registry that we didn't
|
||||
// set ourselves by the installer.
|
||||
return reg
|
||||
}
|
||||
if disk == "" {
|
||||
// Something in the registry is better than nothing on disk.
|
||||
return reg
|
||||
}
|
||||
if disk != def && disk != oldRegDef {
|
||||
// The value in the registry is the old
|
||||
// default (login.tailscale.com) but the value
|
||||
// on disk is neither our old nor new default
|
||||
// value, so it must be some custom thing that
|
||||
// the user cares about. Prefer the disk value.
|
||||
return disk
|
||||
}
|
||||
}
|
||||
if disk != "" {
|
||||
return disk
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// SetDebugLoggingEnabled controls whether spammy debug logging is enabled.
|
||||
func SetDebugLoggingEnabled(v bool) {
|
||||
loggerx.SetDebugLoggingEnabled(v)
|
||||
}
|
||||
Reference in New Issue
Block a user