// 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 }