mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
aeb15dea30
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
|
|
}
|