policy_source.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package source defines interfaces for policy stores,
  4. // facilitates the creation of policy sources, and provides
  5. // functionality for reading policy settings from these sources.
  6. package source
  7. import (
  8. "cmp"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "tailscale.com/types/lazy"
  13. "tailscale.com/util/syspolicy/setting"
  14. )
  15. // ErrStoreClosed is an error returned when attempting to use a [Store] after it
  16. // has been closed.
  17. var ErrStoreClosed = errors.New("the policy store has been closed")
  18. // Store provides methods to read system policy settings from OS-specific storage.
  19. // Implementations must be concurrency-safe, and may also implement
  20. // [Lockable], [Changeable], [Expirable] and [io.Closer].
  21. //
  22. // If a [Store] implementation also implements [io.Closer],
  23. // it will be called by the package to release the resources
  24. // when the store is no longer needed.
  25. type Store interface {
  26. // ReadString returns the value of a [setting.StringValue] with the specified key,
  27. // an [setting.ErrNotConfigured] if the policy setting is not configured, or
  28. // an error on failure.
  29. ReadString(key setting.Key) (string, error)
  30. // ReadUInt64 returns the value of a [setting.IntegerValue] with the specified key,
  31. // an [setting.ErrNotConfigured] if the policy setting is not configured, or
  32. // an error on failure.
  33. ReadUInt64(key setting.Key) (uint64, error)
  34. // ReadBoolean returns the value of a [setting.BooleanValue] with the specified key,
  35. // an [setting.ErrNotConfigured] if the policy setting is not configured, or
  36. // an error on failure.
  37. ReadBoolean(key setting.Key) (bool, error)
  38. // ReadStringArray returns the value of a [setting.StringListValue] with the specified key,
  39. // an [setting.ErrNotConfigured] if the policy setting is not configured, or
  40. // an error on failure.
  41. ReadStringArray(key setting.Key) ([]string, error)
  42. }
  43. // Lockable is an optional interface that [Store] implementations may support.
  44. // Locking a [Store] is not mandatory as [Store] must be concurrency-safe,
  45. // but is recommended to avoid issues where consecutive read calls for related
  46. // policies might return inconsistent results if a policy change occurs between
  47. // the calls. Implementations may use locking to pre-read policies or for
  48. // similar performance optimizations.
  49. type Lockable interface {
  50. // Lock acquires a read lock on the policy store,
  51. // ensuring the store's state remains unchanged while locked.
  52. // Multiple readers can hold the lock simultaneously.
  53. // It returns an error if the store cannot be locked.
  54. Lock() error
  55. // Unlock unlocks the policy store.
  56. // It is a run-time error if the store is not locked on entry to Unlock.
  57. Unlock()
  58. }
  59. // Changeable is an optional interface that [Store] implementations may support
  60. // if the policy settings they contain can be externally changed after being initially read.
  61. type Changeable interface {
  62. // RegisterChangeCallback adds a function that will be called
  63. // whenever there's a policy change in the [Store].
  64. // The returned function can be used to unregister the callback.
  65. RegisterChangeCallback(callback func()) (unregister func(), err error)
  66. }
  67. // Expirable is an optional interface that [Store] implementations may support
  68. // if they can be externally closed or otherwise become invalid while in use.
  69. type Expirable interface {
  70. // Done returns a channel that is closed when the policy [Store] should no longer be used.
  71. // It should return nil if the store never expires.
  72. Done() <-chan struct{}
  73. }
  74. // Source represents a named source of policy settings for a given [setting.PolicyScope].
  75. type Source struct {
  76. name string
  77. scope setting.PolicyScope
  78. store Store
  79. origin *setting.Origin
  80. lazyReader lazy.SyncValue[*Reader]
  81. }
  82. // NewSource returns a new [Source] with the specified name, scope, and store.
  83. func NewSource(name string, scope setting.PolicyScope, store Store) *Source {
  84. return &Source{name: name, scope: scope, store: store, origin: setting.NewNamedOrigin(name, scope)}
  85. }
  86. // Name reports the name of the policy source.
  87. func (s *Source) Name() string {
  88. return s.name
  89. }
  90. // Scope reports the management scope of the policy source.
  91. func (s *Source) Scope() setting.PolicyScope {
  92. return s.scope
  93. }
  94. // Reader returns a [Reader] that reads from this source's [Store].
  95. func (s *Source) Reader() (*Reader, error) {
  96. return s.lazyReader.GetErr(func() (*Reader, error) {
  97. return newReader(s.store, s.origin)
  98. })
  99. }
  100. // Description returns a formatted string with the scope and name of this policy source.
  101. // It can be used for logging or display purposes.
  102. func (s *Source) Description() string {
  103. if s.name != "" {
  104. return fmt.Sprintf("%s (%v)", s.name, s.Scope())
  105. }
  106. return s.Scope().String()
  107. }
  108. // Compare returns an integer comparing s and s2
  109. // by their precedence, following the "last-wins" model.
  110. // The result will be:
  111. //
  112. // -1 if policy settings from s should be processed before policy settings from s2;
  113. // +1 if policy settings from s should be processed after policy settings from s2, overriding s2;
  114. // 0 if the relative processing order of policy settings in s and s2 is unspecified.
  115. func (s *Source) Compare(s2 *Source) int {
  116. return cmp.Compare(s2.Scope().Kind(), s.Scope().Kind())
  117. }
  118. // Close closes the [Source] and the underlying [Store].
  119. func (s *Source) Close() error {
  120. // The [Reader], if any, owns the [Store].
  121. if reader, _ := s.lazyReader.GetErr(func() (*Reader, error) { return nil, ErrStoreClosed }); reader != nil {
  122. return reader.Close()
  123. }
  124. // Otherwise, it is our responsibility to close it.
  125. if closer, ok := s.store.(io.Closer); ok {
  126. return closer.Close()
  127. }
  128. return nil
  129. }