mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 00:01:40 +01:00 
			
		
		
		
	This is step 1 of ~3, breaking up #14720 into reviewable chunks, with the aim to make syspolicy be a build-time configurable feature. In this first (very noisy) step, all the syspolicy string key constants move to a new constant-only (code-free) package. This will make future steps more reviewable, without this movement noise. There are no code or behavior changes here. The future steps of this series can be seen in #14720: removing global funcs from syspolicy resolution and using an interface that's plumbed around instead. Then adding build tags. Updates #12614 Change-Id: If73bf2c28b9c9b1a408fe868b0b6a25b03eeabd1 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
		
			
				
	
	
		
			148 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			148 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/pkey"
 | |
| 	"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 pkey.Key) (string, error)
 | |
| 	// ReadUInt64 returns the value of a [setting.IntegerValue] with the specified key,
 | |
| 	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | |
| 	// an error on failure.
 | |
| 	ReadUInt64(key pkey.Key) (uint64, error)
 | |
| 	// ReadBoolean returns the value of a [setting.BooleanValue] with the specified key,
 | |
| 	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | |
| 	// an error on failure.
 | |
| 	ReadBoolean(key pkey.Key) (bool, error)
 | |
| 	// ReadStringArray returns the value of a [setting.StringListValue] with the specified key,
 | |
| 	// an [setting.ErrNotConfigured] if the policy setting is not configured, or
 | |
| 	// an error on failure.
 | |
| 	ReadStringArray(key pkey.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
 | |
| }
 |