Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package internal contains miscellaneous functions and types
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package loggerx provides logging functions to the rest of the syspolicy packages.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package metrics provides logging and reporting for policy settings and scopes.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package metrics
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package pkey defines the keys used to store system policies in the registry.
|
||||
|
||||
-80
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
|
||||
// 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(pkey.AllowedSuggestedExitNodes, setting.DeviceSetting, setting.StringListValue),
|
||||
setting.NewDefinition(pkey.AllowExitNodeOverride, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(pkey.AllowTailscaledRestart, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(pkey.AlwaysOn, setting.DeviceSetting, setting.BooleanValue),
|
||||
setting.NewDefinition(pkey.AlwaysOnOverrideWithReason, setting.DeviceSetting, setting.BooleanValue),
|
||||
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.EnableDNSRegistration, setting.DeviceSetting, setting.PreferenceOptionValue),
|
||||
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.EncryptState, 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.ReconnectAfter, setting.DeviceSetting, setting.DurationValue),
|
||||
setting.NewDefinition(pkey.Tailnet, setting.DeviceSetting, setting.StringValue),
|
||||
setting.NewDefinition(pkey.HardwareAttestation, setting.DeviceSetting, setting.BooleanValue),
|
||||
|
||||
// User policy settings (can be configured on a user- or device-basis):
|
||||
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() {
|
||||
internal.Init.MustDefer(func() error {
|
||||
// Avoid implicit [setting.Definition] registration during tests.
|
||||
// Each test should control which policy settings to register.
|
||||
// Use [setting.SetDefinitionsForTest] to specify necessary definitions,
|
||||
// or [setWellKnownSettingsForTest] to set implicit definitions for the test duration.
|
||||
if testenv.InTest() {
|
||||
return nil
|
||||
}
|
||||
for _, d := range implicitDefinitions {
|
||||
setting.RegisterDefinition(d)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_syspolicy
|
||||
|
||||
package policyclient
|
||||
|
||||
import "tailscale.com/util/syspolicy/setting"
|
||||
|
||||
// PolicySnapshot is an alias for [settings.Snapshot] unless syspolicy is omitted
|
||||
// from the build.
|
||||
type policySnapshot = setting.Snapshot
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ts_omit_syspolicy
|
||||
|
||||
package policyclient
|
||||
|
||||
// PolicySnapshot is a stub when syspolicy is omitted from the build.
|
||||
type policySnapshot struct{}
|
||||
+19
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package policyclient contains the minimal syspolicy interface as needed by
|
||||
@@ -62,11 +62,21 @@ type Client interface {
|
||||
HasAnyOf(keys ...pkey.Key) (bool, error)
|
||||
|
||||
// RegisterChangeCallback registers a callback function that will be called
|
||||
// whenever a policy change is detected. It returns a function to unregister
|
||||
// the callback and an error if the registration fails.
|
||||
RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error)
|
||||
// whenever a policy change is detected. If uid is empty, the callback fires
|
||||
// for device-scope policy changes. If uid is non-empty, it fires for changes
|
||||
// to that user's effective policy (device + user policies merged). It returns
|
||||
// a function to unregister the callback and an error if the registration fails.
|
||||
RegisterChangeCallback(uid string, cb func(PolicyChange)) (unregister func(), err error)
|
||||
|
||||
// GetPolicySnapshot returns the effective policy snapshot for the given user ID.
|
||||
// If uid is empty, returns the default-scope policy. Returns nil, nil if policy
|
||||
// snapshots are not supported.
|
||||
GetPolicySnapshot(uid string) (*PolicySnapshot, error)
|
||||
}
|
||||
|
||||
// PolicySnapshot is an alias for [setting.Snapshot] unless syspolicy is omitted from the build.
|
||||
type PolicySnapshot = policySnapshot
|
||||
|
||||
// Get returns a non-nil [Client] implementation as a function of the
|
||||
// build tags. It returns a no-op implementation if the full syspolicy
|
||||
// package is omitted from the build, or in tests.
|
||||
@@ -140,6 +150,10 @@ func (NoPolicyClient) HasAnyOf(keys ...pkey.Key) (bool, error) {
|
||||
|
||||
func (NoPolicyClient) SetDebugLoggingEnabled(enabled bool) {}
|
||||
|
||||
func (NoPolicyClient) RegisterChangeCallback(cb func(PolicyChange)) (unregister func(), err error) {
|
||||
func (NoPolicyClient) RegisterChangeCallback(uid string, cb func(PolicyChange)) (unregister func(), err error) {
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (NoPolicyClient) GetPolicySnapshot(uid string) (*PolicySnapshot, error) {
|
||||
return nil, nil
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package ptype contains types used by syspolicy.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package rsop
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package rsop
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package rsop facilitates [source.Store] registration via [RegisterStore]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package rsop
|
||||
|
||||
+3
-5
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -39,7 +37,7 @@ type ErrorText string
|
||||
|
||||
// NewErrorText returns a [ErrorText] with the specified error message.
|
||||
func NewErrorText(text string) *ErrorText {
|
||||
return ptr.To(ErrorText(text))
|
||||
return new(ErrorText(text))
|
||||
}
|
||||
|
||||
// MaybeErrorText returns an [ErrorText] with the text of the specified error,
|
||||
@@ -51,7 +49,7 @@ func MaybeErrorText(err error) *ErrorText {
|
||||
if err, ok := err.(*ErrorText); ok {
|
||||
return err
|
||||
}
|
||||
return ptr.To(ErrorText(err.Error()))
|
||||
return new(ErrorText(err.Error()))
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package setting contains types for defining and representing policy settings.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package setting
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package source
|
||||
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
)
|
||||
|
||||
// hujsonStandardize is set to hujson.Standardize by json_policy_store_hujson.go
|
||||
// on platforms that have HuJSON support compiled in. When non-nil, the JSON
|
||||
// policy file may use HuJSON (comments and trailing commas); when nil, the
|
||||
// file must be pure standard JSON.
|
||||
var hujsonStandardize func([]byte) ([]byte, error)
|
||||
|
||||
var _ Store = (*JSONPolicyStore)(nil)
|
||||
|
||||
// JSONPolicyStore is a [Store] backed by a JSON object that maps policy
|
||||
// setting keys to values. It is a read-only snapshot; the underlying map
|
||||
// is captured at construction time and never re-read.
|
||||
//
|
||||
// JSON values are mapped to policy setting types as follows:
|
||||
// - strings map to [setting.StringValue], [setting.PreferenceOptionValue],
|
||||
// [setting.VisibilityValue], and [setting.DurationValue]. For
|
||||
// [setting.DurationValue], the string is parsed by [time.ParseDuration]
|
||||
// elsewhere in the package (e.g. "24h", "5m").
|
||||
// - booleans map to [setting.BooleanValue].
|
||||
// - numbers map to [setting.IntegerValue]. Negative or non-integer values
|
||||
// are rejected with [setting.ErrTypeMismatch].
|
||||
// - arrays of strings map to [setting.StringListValue].
|
||||
type JSONPolicyStore struct {
|
||||
m map[string]any
|
||||
}
|
||||
|
||||
// NewJSONPolicyStore returns a new [JSONPolicyStore] backed by the given map.
|
||||
// A nil or empty map results in a store that reports every key as
|
||||
// [setting.ErrNotConfigured].
|
||||
func NewJSONPolicyStore(m map[string]any) *JSONPolicyStore {
|
||||
return &JSONPolicyStore{m: m}
|
||||
}
|
||||
|
||||
// NewJSONPolicyStoreFromFile reads the file at path and returns a new
|
||||
// [JSONPolicyStore] backed by its contents. The file must contain a JSON
|
||||
// object at its top level. JSON numbers are decoded as [json.Number] to
|
||||
// preserve precision for [setting.IntegerValue] settings.
|
||||
func NewJSONPolicyStoreFromFile(path string) (*JSONPolicyStore, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewJSONPolicyStoreFromBytes(data)
|
||||
}
|
||||
|
||||
// NewJSONPolicyStoreFromBytes is like [NewJSONPolicyStoreFromFile] but reads
|
||||
// from data instead of a file. When HuJSON support is linked into the build,
|
||||
// data may be HuJSON (comments and trailing commas allowed); otherwise it
|
||||
// must be pure standard JSON.
|
||||
func NewJSONPolicyStoreFromBytes(data []byte) (*JSONPolicyStore, error) {
|
||||
if buildfeatures.HasHuJSONConf && hujsonStandardize != nil {
|
||||
std, err := hujsonStandardize(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("syspolicy: parsing HuJSON/JSON: %w", err)
|
||||
}
|
||||
data = std
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.UseNumber()
|
||||
var m map[string]any
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
return nil, fmt.Errorf("syspolicy: parsing JSON: %w", err)
|
||||
}
|
||||
return &JSONPolicyStore{m: m}, nil
|
||||
}
|
||||
|
||||
// ReadString implements [Store].
|
||||
func (s *JSONPolicyStore) ReadString(key pkey.Key) (string, error) {
|
||||
v, ok := s.m[string(key)]
|
||||
if !ok {
|
||||
return "", setting.ErrNotConfigured
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%w: %q is %T, want string", setting.ErrTypeMismatch, key, v)
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// ReadBoolean implements [Store].
|
||||
func (s *JSONPolicyStore) ReadBoolean(key pkey.Key) (bool, error) {
|
||||
v, ok := s.m[string(key)]
|
||||
if !ok {
|
||||
return false, setting.ErrNotConfigured
|
||||
}
|
||||
b, ok := v.(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("%w: %q is %T, want bool", setting.ErrTypeMismatch, key, v)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// ReadUInt64 implements [Store].
|
||||
func (s *JSONPolicyStore) ReadUInt64(key pkey.Key) (uint64, error) {
|
||||
v, ok := s.m[string(key)]
|
||||
if !ok {
|
||||
return 0, setting.ErrNotConfigured
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case json.Number:
|
||||
u, err := strconv.ParseUint(n.String(), 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: %q is not a uint64: %v", setting.ErrTypeMismatch, key, err)
|
||||
}
|
||||
return u, nil
|
||||
case float64:
|
||||
if n < 0 || n > math.MaxUint64 || n != math.Trunc(n) {
|
||||
return 0, fmt.Errorf("%w: %q (%v) is not a uint64", setting.ErrTypeMismatch, key, n)
|
||||
}
|
||||
return uint64(n), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("%w: %q is %T, want number", setting.ErrTypeMismatch, key, v)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadStringArray implements [Store].
|
||||
func (s *JSONPolicyStore) ReadStringArray(key pkey.Key) ([]string, error) {
|
||||
v, ok := s.m[string(key)]
|
||||
if !ok {
|
||||
return nil, setting.ErrNotConfigured
|
||||
}
|
||||
arr, ok := v.([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %q is %T, want array", setting.ErrTypeMismatch, key, v)
|
||||
}
|
||||
res := make([]string, len(arr))
|
||||
for i, e := range arr {
|
||||
str, ok := e.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %q[%d] is %T, want string", setting.ErrTypeMismatch, key, i, e)
|
||||
}
|
||||
res[i] = str
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Validate checks that every key in the parsed JSON corresponds to a
|
||||
// registered policy setting (per [setting.Definitions]) and that its value
|
||||
// can be successfully decoded as the registered setting's type. It joins
|
||||
// all problems into a single error so callers see every issue at once
|
||||
// instead of one per startup-then-runtime cycle.
|
||||
//
|
||||
// Validate triggers registration of any deferred setting definitions, so
|
||||
// it should only be called after all init-time registrations have run.
|
||||
func (s *JSONPolicyStore) Validate() error {
|
||||
defs, err := setting.Definitions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
byKey := make(map[pkey.Key]*setting.Definition, len(defs))
|
||||
for _, d := range defs {
|
||||
byKey[d.Key()] = d
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(s.m))
|
||||
for k := range s.m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var errs []error
|
||||
for _, k := range keys {
|
||||
def, ok := byKey[pkey.Key(k)]
|
||||
if !ok {
|
||||
errs = append(errs, fmt.Errorf("unknown policy setting %q", k))
|
||||
continue
|
||||
}
|
||||
if err := s.validateValue(def); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%q: %w", k, err))
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// validateValue is like [readPolicySettingValue] but is stricter for the
|
||||
// enum-like [setting.PreferenceOptionValue] and [setting.VisibilityValue]
|
||||
// types: their runtime UnmarshalText silently coerces unknown strings to a
|
||||
// default, which is fine at read time but defeats the point of load-time
|
||||
// validation, so this checks the raw string against the known values.
|
||||
func (s *JSONPolicyStore) validateValue(def *setting.Definition) error {
|
||||
key := def.Key()
|
||||
switch def.Type() {
|
||||
case setting.PreferenceOptionValue:
|
||||
str, err := s.ReadString(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch str {
|
||||
case "always", "never", "user-decides":
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`%w: %q is not a valid PreferenceOption ("always", "never", or "user-decides")`, setting.ErrTypeMismatch, str)
|
||||
case setting.VisibilityValue:
|
||||
str, err := s.ReadString(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch str {
|
||||
case "show", "hide":
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`%w: %q is not a valid Visibility ("show" or "hide")`, setting.ErrTypeMismatch, str)
|
||||
default:
|
||||
_, err := readPolicySettingValue(s, def)
|
||||
return err
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !android && !ts_omit_hujsonconf
|
||||
|
||||
package source
|
||||
|
||||
import "github.com/tailscale/hujson"
|
||||
|
||||
// Only link the hujson package on platforms that use it, to reduce binary
|
||||
// size & memory a bit. iOS and Android don't load syspolicy files from
|
||||
// disk so they don't need HuJSON either.
|
||||
//
|
||||
// While the linker's deadcode mostly handles this today, this keeps us
|
||||
// honest for the future.
|
||||
|
||||
func init() {
|
||||
hujsonStandardize = hujson.Standardize
|
||||
}
|
||||
+10
-4
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package source
|
||||
@@ -91,7 +91,7 @@ func newReader(store Store, origin *setting.Origin) (*Reader, error) {
|
||||
}
|
||||
|
||||
// GetSettings returns the current [*setting.Snapshot],
|
||||
// re-reading it from from the underlying [Store] only if the policy
|
||||
// re-reading it from the underlying [Store] only if the policy
|
||||
// has changed since it was read last. It never fails and returns
|
||||
// the previous version of the policy settings if a read attempt fails.
|
||||
func (r *Reader) GetSettings() *setting.Snapshot {
|
||||
@@ -125,6 +125,11 @@ func (r *Reader) ReadSettings() (*setting.Snapshot, error) {
|
||||
func (r *Reader) reload(force bool) (*setting.Snapshot, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.closing {
|
||||
// The reader is closing (or already closed) and r.store may be nil.
|
||||
// Return the last known policy instead of reading from the store.
|
||||
return r.lastPolicy, nil
|
||||
}
|
||||
if r.upToDate && !force {
|
||||
return r.lastPolicy, nil
|
||||
}
|
||||
@@ -267,12 +272,13 @@ func (r *Reader) Close() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.store = nil
|
||||
|
||||
close(r.doneCh)
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
// Nil out the store only while holding r.mu; reload reads r.store
|
||||
// under the same lock, so writing it unlocked would be a data race.
|
||||
r.store = nil
|
||||
for _, c := range r.sessions {
|
||||
c.closeInternal()
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package source defines interfaces for policy stores,
|
||||
|
||||
+29
-17
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package source
|
||||
@@ -117,12 +117,12 @@ func NewUserPlatformPolicyStore(token windows.Token) (*PlatformPolicyStore, erro
|
||||
return newPlatformPolicyStore(gp.UserPolicy, softwareKey, policyLock), nil
|
||||
}
|
||||
|
||||
func newPlatformPolicyStore(scope gp.Scope, softwareKey registry.Key, policyLock *gp.PolicyLock) *PlatformPolicyStore {
|
||||
func newPlatformPolicyStore(scope gp.Scope, softwareKey registry.Key, policyLock lockableCloser) *PlatformPolicyStore {
|
||||
return &PlatformPolicyStore{
|
||||
scope: scope,
|
||||
softwareKey: softwareKey,
|
||||
done: make(chan struct{}),
|
||||
policyLock: &optionalPolicyLock{PolicyLock: policyLock},
|
||||
policyLock: &optionalPolicyLock{lockableCloser: policyLock},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,13 +468,14 @@ type gpLockState int
|
||||
const (
|
||||
gpUnlocked = gpLockState(iota)
|
||||
gpLocked
|
||||
gpLockRestricted // the lock could not be acquired due to a restriction in place
|
||||
gpLockSkipped // the lock was not acquired, but reading proceeds without it
|
||||
)
|
||||
|
||||
// optionalPolicyLock is a wrapper around [gp.PolicyLock] that locks
|
||||
// and unlocks the underlying [gp.PolicyLock].
|
||||
//
|
||||
// If the [gp.PolicyLock.Lock] returns [gp.ErrLockRestricted], the error is ignored,
|
||||
// If [gp.PolicyLock.Lock] returns [gp.ErrLockRestricted] or
|
||||
// [windows.ERROR_ACCESS_DENIED], the error is ignored,
|
||||
// and calling [optionalPolicyLock.Unlock] is a no-op.
|
||||
//
|
||||
// The underlying GP lock is kinda optional: it is safe to read policy settings
|
||||
@@ -483,27 +484,38 @@ const (
|
||||
//
|
||||
// It is not safe for concurrent use.
|
||||
type optionalPolicyLock struct {
|
||||
*gp.PolicyLock
|
||||
state gpLockState
|
||||
lockableCloser // typically a [*gp.PolicyLock]
|
||||
state gpLockState
|
||||
}
|
||||
|
||||
// Lock acquires the underlying [gp.PolicyLock], returning an error on failure.
|
||||
//
|
||||
// If the lock cannot be acquired due to a restriction in place
|
||||
// (e.g., attempting to acquire a lock while the service is starting),
|
||||
// the lock is considered to be held, the method returns nil, and a subsequent
|
||||
// call to [Unlock] is a no-op.
|
||||
// or because the caller is not allowed to acquire it, the lock is considered
|
||||
// to be held, the method returns nil, and a subsequent call to [Unlock] is a no-op.
|
||||
// The latter happens with [windows.ERROR_ACCESS_DENIED] when a user policy lock
|
||||
// is requested by a process whose user has no interactive logon session, such as
|
||||
// tailscaled started from an SSH session; policy settings are then read
|
||||
// without the lock.
|
||||
//
|
||||
// It is a runtime error to call Lock when the lock is already held.
|
||||
func (o *optionalPolicyLock) Lock() error {
|
||||
if o.state != gpUnlocked {
|
||||
panic("already locked")
|
||||
}
|
||||
switch err := o.PolicyLock.Lock(); err {
|
||||
case nil:
|
||||
err := o.lockableCloser.Lock()
|
||||
switch {
|
||||
case err == nil:
|
||||
o.state = gpLocked
|
||||
return nil
|
||||
case gp.ErrLockRestricted:
|
||||
case errors.Is(err, gp.ErrLockRestricted):
|
||||
loggerx.Errorf("GP lock not acquired: %v", err)
|
||||
o.state = gpLockRestricted
|
||||
o.state = gpLockSkipped
|
||||
return nil
|
||||
case errors.Is(err, windows.ERROR_ACCESS_DENIED):
|
||||
loggerx.Errorf("GP lock not acquired (%v); reading policy settings without it", err)
|
||||
o.state = gpLockSkipped
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
@@ -515,10 +527,10 @@ func (o *optionalPolicyLock) Lock() error {
|
||||
func (o *optionalPolicyLock) Unlock() {
|
||||
switch o.state {
|
||||
case gpLocked:
|
||||
o.PolicyLock.Unlock()
|
||||
case gpLockRestricted:
|
||||
// The GP lock wasn't acquired due to a restriction in place
|
||||
// when [optionalPolicyLock.Lock] was called. Unlock is a no-op.
|
||||
o.lockableCloser.Unlock()
|
||||
case gpLockSkipped:
|
||||
// The GP lock wasn't acquired when [optionalPolicyLock.Lock]
|
||||
// was called. Unlock is a no-op.
|
||||
case gpUnlocked:
|
||||
panic("not locked")
|
||||
default:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package source
|
||||
|
||||
-264
@@ -1,264 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package syspolicy contains the implementation of system policy management.
|
||||
// Calling code should use the client interface in
|
||||
// tailscale.com/util/syspolicy/policyclient.
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/syspolicy/internal/loggerx"
|
||||
"tailscale.com/util/syspolicy/pkey"
|
||||
"tailscale.com/util/syspolicy/policyclient"
|
||||
"tailscale.com/util/syspolicy/ptype"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotConfigured is returned when the requested policy setting is not configured.
|
||||
ErrNotConfigured = setting.ErrNotConfigured
|
||||
// ErrTypeMismatch is returned when there's a type mismatch between the actual type
|
||||
// of the setting value and the expected type.
|
||||
ErrTypeMismatch = setting.ErrTypeMismatch
|
||||
// ErrNoSuchKey is returned by [setting.DefinitionOf] when no policy setting
|
||||
// has been registered with the specified key.
|
||||
//
|
||||
// This error is also returned by a (now deprecated) [Handler] when the specified
|
||||
// key does 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 = setting.ErrNoSuchKey
|
||||
)
|
||||
|
||||
// RegisterStore registers a new policy [source.Store] with the specified name and [setting.PolicyScope].
|
||||
//
|
||||
// It is a shorthand for [rsop.RegisterStore].
|
||||
func RegisterStore(name string, scope setting.PolicyScope, store source.Store) (*rsop.StoreRegistration, error) {
|
||||
return rsop.RegisterStore(name, scope, store)
|
||||
}
|
||||
|
||||
// hasAnyOf returns whether at least one of the specified policy settings is configured,
|
||||
// or an error if no keys are provided or the check fails.
|
||||
func hasAnyOf(keys ...pkey.Key) (bool, error) {
|
||||
if len(keys) == 0 {
|
||||
return false, errors.New("at least one key must be specified")
|
||||
}
|
||||
policy, err := rsop.PolicyFor(setting.DefaultScope())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
effective := policy.Get()
|
||||
for _, k := range keys {
|
||||
_, err := effective.GetErr(k)
|
||||
if errors.Is(err, setting.ErrNotConfigured) || errors.Is(err, setting.ErrNoSuchKey) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// getString returns a string policy setting with the specified key,
|
||||
// or defaultValue if it does not exist.
|
||||
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 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 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 pkey.Key, defaultValue []string) ([]string, error) {
|
||||
return getCurrentPolicySettingValue(key, defaultValue)
|
||||
}
|
||||
|
||||
// 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, defaultValue (and a nil error) is returned.
|
||||
func getPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
|
||||
return getCurrentPolicySettingValue(name, defaultValue)
|
||||
}
|
||||
|
||||
// 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 pkey.Key) (ptype.Visibility, error) {
|
||||
return getCurrentPolicySettingValue(name, ptype.VisibleByPolicy)
|
||||
}
|
||||
|
||||
// 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 pkey.Key, defaultValue time.Duration) (time.Duration, error) {
|
||||
d, err := getCurrentPolicySettingValue(name, defaultValue)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
if d < 0 {
|
||||
return defaultValue, nil
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// registerChangeCallback adds a function that will be called whenever the effective policy
|
||||
// for the default scope changes. The returned function can be used to unregister the callback.
|
||||
func registerChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), err error) {
|
||||
effective, err := rsop.PolicyFor(setting.DefaultScope())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return effective.RegisterChangeCallback(cb), nil
|
||||
}
|
||||
|
||||
// getCurrentPolicySettingValue returns the value of the policy setting
|
||||
// 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 pkey.Key, def T) (T, error) {
|
||||
effective, err := rsop.PolicyFor(setting.DefaultScope())
|
||||
if err != nil {
|
||||
return def, err
|
||||
}
|
||||
value, err := effective.Get().GetErr(key)
|
||||
if err != nil {
|
||||
if errors.Is(err, setting.ErrNotConfigured) || errors.Is(err, setting.ErrNoSuchKey) {
|
||||
return def, nil
|
||||
}
|
||||
return def, err
|
||||
}
|
||||
if res, ok := value.(T); ok {
|
||||
return res, nil
|
||||
}
|
||||
return convertPolicySettingValueTo(value, def)
|
||||
}
|
||||
|
||||
func convertPolicySettingValueTo[T setting.ValueType](value any, def T) (T, error) {
|
||||
// Convert [PreferenceOption], [Visibility], or [time.Duration] back to a string
|
||||
// if someone requests a string instead of the actual setting's value.
|
||||
// TODO(nickkhyl): check if this behavior is relied upon anywhere besides the old tests.
|
||||
if reflect.TypeFor[T]().Kind() == reflect.String {
|
||||
if str, ok := value.(fmt.Stringer); ok {
|
||||
return any(str.String()).(T), nil
|
||||
}
|
||||
}
|
||||
return def, fmt.Errorf("%w: got %T, want %T", setting.ErrTypeMismatch, value, def)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func init() {
|
||||
policyclient.RegisterClientImpl(globalSyspolicy{})
|
||||
}
|
||||
|
||||
// globalSyspolicy implements [policyclient.Client] using the syspolicy global
|
||||
// functions and global registrations.
|
||||
//
|
||||
// TODO: de-global-ify. This implementation using the old global functions
|
||||
// is an intermediate stage while changing policyclient to be modular.
|
||||
type globalSyspolicy struct{}
|
||||
|
||||
func (globalSyspolicy) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
|
||||
return getBoolean(key, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetString(key pkey.Key, defaultValue string) (string, error) {
|
||||
return getString(key, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
|
||||
return getStringArray(key, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) SetDebugLoggingEnabled(enabled bool) {
|
||||
loggerx.SetDebugLoggingEnabled(enabled)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
|
||||
return getUint64(key, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
|
||||
return getDuration(name, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
|
||||
return getPreferenceOption(name, defaultValue)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) GetVisibility(name pkey.Key) (ptype.Visibility, error) {
|
||||
return getVisibility(name)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) HasAnyOf(keys ...pkey.Key) (bool, error) {
|
||||
return hasAnyOf(keys...)
|
||||
}
|
||||
|
||||
func (globalSyspolicy) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) {
|
||||
return registerChangeCallback(cb)
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package syspolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/user"
|
||||
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
"tailscale.com/util/syspolicy/rsop"
|
||||
"tailscale.com/util/syspolicy/setting"
|
||||
"tailscale.com/util/syspolicy/source"
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// On Windows, we should automatically register the Registry-based policy
|
||||
// store for the device. If we are running in a user's security context
|
||||
// (e.g., we're the GUI), we should also register the Registry policy store for
|
||||
// the user. In the future, we should register (and unregister) user policy
|
||||
// stores whenever a user connects to (or disconnects from) the local backend.
|
||||
// This ensures the backend is aware of the user's policy settings and can send
|
||||
// them to the GUI/CLI/Web clients on demand or whenever they change.
|
||||
//
|
||||
// Other platforms, such as macOS, iOS and Android, should register their
|
||||
// platform-specific policy stores via [RegisterStore]
|
||||
// (or [RegisterHandler] until they implement the [source.Store] interface).
|
||||
//
|
||||
// External code, such as the ipnlocal package, may choose to register
|
||||
// additional policy stores, such as config files and policies received from
|
||||
// the control plane.
|
||||
internal.Init.MustDefer(func() error {
|
||||
// Do not register or use default policy stores during tests.
|
||||
// Each test should set up its own necessary configurations.
|
||||
if testenv.InTest() {
|
||||
return nil
|
||||
}
|
||||
return configureSyspolicy(nil)
|
||||
})
|
||||
}
|
||||
|
||||
// configureSyspolicy configures syspolicy for use on Windows,
|
||||
// either in test or regular builds depending on whether tb has a non-nil value.
|
||||
func configureSyspolicy(tb testenv.TB) error {
|
||||
const localSystemSID = "S-1-5-18"
|
||||
// Always create and register a machine policy store that reads
|
||||
// policy settings from the HKEY_LOCAL_MACHINE registry hive.
|
||||
machineStore, err := source.NewMachinePlatformPolicyStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the machine policy store: %v", err)
|
||||
}
|
||||
if tb == nil {
|
||||
_, err = rsop.RegisterStore("Platform", setting.DeviceScope, machineStore)
|
||||
} else {
|
||||
_, err = rsop.RegisterStoreForTest(tb, "Platform", setting.DeviceScope, machineStore)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check whether the current process is running as Local System or not.
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Uid == localSystemSID {
|
||||
return nil
|
||||
}
|
||||
// If it's not a Local System's process (e.g., it's the GUI rather than the tailscaled service),
|
||||
// we should create and use a policy store for the current user that reads
|
||||
// policy settings from that user's registry hive (HKEY_CURRENT_USER).
|
||||
userStore, err := source.NewUserPlatformPolicyStore(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the current user's policy store: %v", err)
|
||||
}
|
||||
if tb == nil {
|
||||
_, err = rsop.RegisterStore("Platform", setting.CurrentUserScope, userStore)
|
||||
} else {
|
||||
_, err = rsop.RegisterStoreForTest(tb, "Platform", setting.CurrentUserScope, userStore)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// And also set [setting.CurrentUserScope] as the [setting.DefaultScope], so [GetString],
|
||||
// [GetVisibility] and similar functions would be returning a merged result
|
||||
// of the machine's and user's policies.
|
||||
if !setting.SetDefaultScope(setting.CurrentUserScope) {
|
||||
return errors.New("current scope already set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user