mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 10:11:18 +01:00 
			
		
		
		
	We add package defining interfaces for policy stores, enabling creation of policy sources and reading settings from them. It includes a Windows-specific PlatformPolicyStore for GP and MDM policies stored in the Registry, and an in-memory TestStore for testing purposes. We also include an internal package that tracks and reports policy usage metrics when a policy setting is read from a store. Initially, it will be used only on Windows and Android, as macOS, iOS, and tvOS report their own metrics. However, we plan to use it across all platforms eventually. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
		
			
				
	
	
		
			147 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
// Package source defines interfaces for policy stores,
 | 
						|
// facilitates the creation of policy sources, and provides
 | 
						|
// functionality for reading policy settings from these sources.
 | 
						|
package source
 | 
						|
 | 
						|
import (
 | 
						|
	"cmp"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
 | 
						|
	"tailscale.com/types/lazy"
 | 
						|
	"tailscale.com/util/syspolicy/setting"
 | 
						|
)
 | 
						|
 | 
						|
// ErrStoreClosed is an error returned when attempting to use a [Store] after it
 | 
						|
// has been closed.
 | 
						|
var ErrStoreClosed = errors.New("the policy store has been closed")
 | 
						|
 | 
						|
// Store provides methods to read system policy settings from OS-specific storage.
 | 
						|
// Implementations must be concurrency-safe, and may also implement
 | 
						|
// [Lockable], [Changeable], [Expirable] and [io.Closer].
 | 
						|
//
 | 
						|
// If a [Store] implementation also implements [io.Closer],
 | 
						|
// it will be called by the package to release the resources
 | 
						|
// when the store is no longer needed.
 | 
						|
type Store interface {
 | 
						|
	// ReadString returns the value of a [setting.StringValue] with the specified key,
 | 
						|
	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | 
						|
	// an error on failure.
 | 
						|
	ReadString(key setting.Key) (string, error)
 | 
						|
	// ReadUInt64 returns the value of a [setting.IntegerValue] with the specified key,
 | 
						|
	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | 
						|
	// an error on failure.
 | 
						|
	ReadUInt64(key setting.Key) (uint64, error)
 | 
						|
	// ReadBoolean returns the value of a [setting.BooleanValue] with the specified key,
 | 
						|
	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | 
						|
	// an error on failure.
 | 
						|
	ReadBoolean(key setting.Key) (bool, error)
 | 
						|
	// ReadStringArray returns the value of a [setting.StringListValue] with the specified key,
 | 
						|
	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | 
						|
	// an error on failure.
 | 
						|
	ReadStringArray(key setting.Key) ([]string, error)
 | 
						|
}
 | 
						|
 | 
						|
// Lockable is an optional interface that [Store] implementations may support.
 | 
						|
// Locking a [Store] is not mandatory as [Store] must be concurrency-safe,
 | 
						|
// but is recommended to avoid issues where consecutive read calls for related
 | 
						|
// policies might return inconsistent results if a policy change occurs between
 | 
						|
// the calls. Implementations may use locking to pre-read policies or for
 | 
						|
// similar performance optimizations.
 | 
						|
type Lockable interface {
 | 
						|
	// Lock acquires a read lock on the policy store,
 | 
						|
	// ensuring the store's state remains unchanged while locked.
 | 
						|
	// Multiple readers can hold the lock simultaneously.
 | 
						|
	// It returns an error if the store cannot be locked.
 | 
						|
	Lock() error
 | 
						|
	// Unlock unlocks the policy store.
 | 
						|
	// It is a run-time error if the store is not locked on entry to Unlock.
 | 
						|
	Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// Changeable is an optional interface that [Store] implementations may support
 | 
						|
// if the policy settings they contain can be externally changed after being initially read.
 | 
						|
type Changeable interface {
 | 
						|
	// RegisterChangeCallback adds a function that will be called
 | 
						|
	// whenever there's a policy change in the [Store].
 | 
						|
	// The returned function can be used to unregister the callback.
 | 
						|
	RegisterChangeCallback(callback func()) (unregister func(), err error)
 | 
						|
}
 | 
						|
 | 
						|
// Expirable is an optional interface that [Store] implementations may support
 | 
						|
// if they can be externally closed or otherwise become invalid while in use.
 | 
						|
type Expirable interface {
 | 
						|
	// Done returns a channel that is closed when the policy [Store] should no longer be used.
 | 
						|
	// It should return nil if the store never expires.
 | 
						|
	Done() <-chan struct{}
 | 
						|
}
 | 
						|
 | 
						|
// Source represents a named source of policy settings for a given [setting.PolicyScope].
 | 
						|
type Source struct {
 | 
						|
	name   string
 | 
						|
	scope  setting.PolicyScope
 | 
						|
	store  Store
 | 
						|
	origin *setting.Origin
 | 
						|
 | 
						|
	lazyReader lazy.SyncValue[*Reader]
 | 
						|
}
 | 
						|
 | 
						|
// NewSource returns a new [Source] with the specified name, scope, and store.
 | 
						|
func NewSource(name string, scope setting.PolicyScope, store Store) *Source {
 | 
						|
	return &Source{name: name, scope: scope, store: store, origin: setting.NewNamedOrigin(name, scope)}
 | 
						|
}
 | 
						|
 | 
						|
// Name reports the name of the policy source.
 | 
						|
func (s *Source) Name() string {
 | 
						|
	return s.name
 | 
						|
}
 | 
						|
 | 
						|
// Scope reports the management scope of the policy source.
 | 
						|
func (s *Source) Scope() setting.PolicyScope {
 | 
						|
	return s.scope
 | 
						|
}
 | 
						|
 | 
						|
// Reader returns a [Reader] that reads from this source's [Store].
 | 
						|
func (s *Source) Reader() (*Reader, error) {
 | 
						|
	return s.lazyReader.GetErr(func() (*Reader, error) {
 | 
						|
		return newReader(s.store, s.origin)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Description returns a formatted string with the scope and name of this policy source.
 | 
						|
// It can be used for logging or display purposes.
 | 
						|
func (s *Source) Description() string {
 | 
						|
	if s.name != "" {
 | 
						|
		return fmt.Sprintf("%s (%v)", s.name, s.Scope())
 | 
						|
	}
 | 
						|
	return s.Scope().String()
 | 
						|
}
 | 
						|
 | 
						|
// Compare returns an integer comparing s and s2
 | 
						|
// by their precedence, following the "last-wins" model.
 | 
						|
// The result will be:
 | 
						|
//
 | 
						|
//	-1 if policy settings from s should be processed before policy settings from s2;
 | 
						|
//	+1 if policy settings from s should be processed after policy settings from s2, overriding s2;
 | 
						|
//	0 if the relative processing order of policy settings in s and s2 is unspecified.
 | 
						|
func (s *Source) Compare(s2 *Source) int {
 | 
						|
	return cmp.Compare(s2.Scope().Kind(), s.Scope().Kind())
 | 
						|
}
 | 
						|
 | 
						|
// Close closes the [Source] and the underlying [Store].
 | 
						|
func (s *Source) Close() error {
 | 
						|
	// The [Reader], if any, owns the [Store].
 | 
						|
	if reader, _ := s.lazyReader.GetErr(func() (*Reader, error) { return nil, ErrStoreClosed }); reader != nil {
 | 
						|
		return reader.Close()
 | 
						|
	}
 | 
						|
	// Otherwise, it is our responsibility to close it.
 | 
						|
	if closer, ok := s.store.(io.Closer); ok {
 | 
						|
		return closer.Close()
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |