setting.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package setting contains types for defining and representing policy settings.
  4. // It facilitates the registration of setting definitions using [Register] and [RegisterDefinition],
  5. // and the retrieval of registered setting definitions via [Definitions] and [DefinitionOf].
  6. // This package is intended for use primarily within the syspolicy package hierarchy.
  7. package setting
  8. import (
  9. "fmt"
  10. "slices"
  11. "strings"
  12. "time"
  13. "tailscale.com/syncs"
  14. "tailscale.com/types/lazy"
  15. "tailscale.com/util/syspolicy/internal"
  16. "tailscale.com/util/syspolicy/pkey"
  17. "tailscale.com/util/syspolicy/ptype"
  18. "tailscale.com/util/testenv"
  19. )
  20. // Scope indicates the broadest scope at which a policy setting may apply,
  21. // and the narrowest scope at which it may be configured.
  22. type Scope int8
  23. const (
  24. // DeviceSetting indicates a policy setting that applies to a device, regardless of
  25. // which OS user or Tailscale profile is currently active, if any.
  26. // It can only be configured at a [DeviceScope].
  27. DeviceSetting Scope = iota
  28. // ProfileSetting indicates a policy setting that applies to a Tailscale profile.
  29. // It can only be configured for a specific profile or at a [DeviceScope],
  30. // in which case it applies to all profiles on the device.
  31. ProfileSetting
  32. // UserSetting indicates a policy setting that applies to users.
  33. // It can be configured for a user, profile, or the entire device.
  34. UserSetting
  35. // NumScopes is the number of possible [Scope] values.
  36. NumScopes int = iota // must be the last value in the const block.
  37. )
  38. // String implements [fmt.Stringer].
  39. func (s Scope) String() string {
  40. switch s {
  41. case DeviceSetting:
  42. return "Device"
  43. case ProfileSetting:
  44. return "Profile"
  45. case UserSetting:
  46. return "User"
  47. default:
  48. panic("unreachable")
  49. }
  50. }
  51. // MarshalText implements [encoding.TextMarshaler].
  52. func (s Scope) MarshalText() (text []byte, err error) {
  53. return []byte(s.String()), nil
  54. }
  55. // UnmarshalText implements [encoding.TextUnmarshaler].
  56. func (s *Scope) UnmarshalText(text []byte) error {
  57. switch strings.ToLower(string(text)) {
  58. case "device":
  59. *s = DeviceSetting
  60. case "profile":
  61. *s = ProfileSetting
  62. case "user":
  63. *s = UserSetting
  64. default:
  65. return fmt.Errorf("%q is not a valid scope", string(text))
  66. }
  67. return nil
  68. }
  69. // Type is a policy setting value type.
  70. // Except for [InvalidValue], which represents an invalid policy setting type,
  71. // and [PreferenceOptionValue], [VisibilityValue], and [DurationValue],
  72. // which have special handling due to their legacy status in the package,
  73. // SettingTypes represent the raw value types readable from policy stores.
  74. type Type int
  75. const (
  76. // InvalidValue indicates an invalid policy setting value type.
  77. InvalidValue Type = iota
  78. // BooleanValue indicates a policy setting whose underlying type is a bool.
  79. BooleanValue
  80. // IntegerValue indicates a policy setting whose underlying type is a uint64.
  81. IntegerValue
  82. // StringValue indicates a policy setting whose underlying type is a string.
  83. StringValue
  84. // StringListValue indicates a policy setting whose underlying type is a []string.
  85. StringListValue
  86. // PreferenceOptionValue indicates a three-state policy setting whose
  87. // underlying type is a string, but the actual value is a [PreferenceOption].
  88. PreferenceOptionValue
  89. // VisibilityValue indicates a two-state boolean-like policy setting whose
  90. // underlying type is a string, but the actual value is a [Visibility].
  91. VisibilityValue
  92. // DurationValue indicates an interval/period/duration policy setting whose
  93. // underlying type is a string, but the actual value is a [time.Duration].
  94. DurationValue
  95. )
  96. // String returns a string representation of t.
  97. func (t Type) String() string {
  98. switch t {
  99. case InvalidValue:
  100. return "Invalid"
  101. case BooleanValue:
  102. return "Boolean"
  103. case IntegerValue:
  104. return "Integer"
  105. case StringValue:
  106. return "String"
  107. case StringListValue:
  108. return "StringList"
  109. case PreferenceOptionValue:
  110. return "PreferenceOption"
  111. case VisibilityValue:
  112. return "Visibility"
  113. case DurationValue:
  114. return "Duration"
  115. default:
  116. panic("unreachable")
  117. }
  118. }
  119. // ValueType is a constraint that allows Go types corresponding to [Type].
  120. type ValueType interface {
  121. bool | uint64 | string | []string | ptype.Visibility | ptype.PreferenceOption | time.Duration
  122. }
  123. // Definition defines policy key, scope and value type.
  124. type Definition struct {
  125. key pkey.Key
  126. scope Scope
  127. typ Type
  128. platforms PlatformList
  129. }
  130. // NewDefinition returns a new [Definition] with the specified
  131. // key, scope, type and supported platforms (see [PlatformList]).
  132. func NewDefinition(k pkey.Key, s Scope, t Type, platforms ...string) *Definition {
  133. return &Definition{key: k, scope: s, typ: t, platforms: platforms}
  134. }
  135. // Key returns a policy setting's identifier.
  136. func (d *Definition) Key() pkey.Key {
  137. if d == nil {
  138. return ""
  139. }
  140. return d.key
  141. }
  142. // Scope reports the broadest [Scope] the policy setting may apply to.
  143. func (d *Definition) Scope() Scope {
  144. if d == nil {
  145. return 0
  146. }
  147. return d.scope
  148. }
  149. // Type reports the underlying value type of the policy setting.
  150. func (d *Definition) Type() Type {
  151. if d == nil {
  152. return InvalidValue
  153. }
  154. return d.typ
  155. }
  156. // IsSupported reports whether the policy setting is supported on the current OS.
  157. func (d *Definition) IsSupported() bool {
  158. if d == nil {
  159. return false
  160. }
  161. return d.platforms.HasCurrent()
  162. }
  163. // SupportedPlatforms reports platforms on which the policy setting is supported.
  164. // An empty [PlatformList] indicates that s is available on all platforms.
  165. func (d *Definition) SupportedPlatforms() PlatformList {
  166. if d == nil {
  167. return nil
  168. }
  169. return d.platforms
  170. }
  171. // String implements [fmt.Stringer].
  172. func (d *Definition) String() string {
  173. if d == nil {
  174. return "(nil)"
  175. }
  176. return fmt.Sprintf("%v(%q, %v)", d.scope, d.key, d.typ)
  177. }
  178. // Equal reports whether d and d2 have the same key, type and scope.
  179. // It does not check whether both s and s2 are supported on the same platforms.
  180. func (d *Definition) Equal(d2 *Definition) bool {
  181. if d == d2 {
  182. return true
  183. }
  184. if d == nil || d2 == nil {
  185. return false
  186. }
  187. return d.key == d2.key && d.typ == d2.typ && d.scope == d2.scope
  188. }
  189. // DefinitionMap is a map of setting [Definition] by [Key].
  190. type DefinitionMap map[pkey.Key]*Definition
  191. var (
  192. definitions lazy.SyncValue[DefinitionMap]
  193. definitionsMu syncs.Mutex
  194. definitionsList []*Definition
  195. definitionsUsed bool
  196. )
  197. // Register registers a policy setting with the specified key, scope, value type,
  198. // and an optional list of supported platforms. All policy settings must be
  199. // registered before any of them can be used. Register panics if called after
  200. // invoking any functions that use the registered policy definitions. This
  201. // includes calling [Definitions] or [DefinitionOf] directly, or reading any
  202. // policy settings via syspolicy.
  203. func Register(k pkey.Key, s Scope, t Type, platforms ...string) {
  204. RegisterDefinition(NewDefinition(k, s, t, platforms...))
  205. }
  206. // RegisterDefinition is like [Register], but accepts a [Definition].
  207. func RegisterDefinition(d *Definition) {
  208. definitionsMu.Lock()
  209. defer definitionsMu.Unlock()
  210. registerLocked(d)
  211. }
  212. func registerLocked(d *Definition) {
  213. if definitionsUsed {
  214. panic("policy definitions are already in use")
  215. }
  216. definitionsList = append(definitionsList, d)
  217. }
  218. func settingDefinitions() (DefinitionMap, error) {
  219. return definitions.GetErr(func() (DefinitionMap, error) {
  220. if err := internal.Init.Do(); err != nil {
  221. return nil, err
  222. }
  223. definitionsMu.Lock()
  224. defer definitionsMu.Unlock()
  225. definitionsUsed = true
  226. return DefinitionMapOf(definitionsList)
  227. })
  228. }
  229. // DefinitionMapOf returns a [DefinitionMap] with the specified settings,
  230. // or an error if any settings have the same key but different type or scope.
  231. func DefinitionMapOf(settings []*Definition) (DefinitionMap, error) {
  232. m := make(DefinitionMap, len(settings))
  233. for _, s := range settings {
  234. if existing, exists := m[s.key]; exists {
  235. if existing.Equal(s) {
  236. // Ignore duplicate setting definitions if they match. It is acceptable
  237. // if the same policy setting was registered more than once
  238. // (e.g. by the syspolicy package itself and by iOS/Android code).
  239. existing.platforms.mergeFrom(s.platforms)
  240. continue
  241. }
  242. return nil, fmt.Errorf("duplicate policy definition: %q", s.key)
  243. }
  244. m[s.key] = s
  245. }
  246. return m, nil
  247. }
  248. // SetDefinitionsForTest allows to register the specified setting definitions
  249. // for the test duration. It is not concurrency-safe, but unlike [Register],
  250. // it does not panic and can be called anytime.
  251. // It returns an error if ds contains two different settings with the same [Key].
  252. func SetDefinitionsForTest(tb testenv.TB, ds ...*Definition) error {
  253. m, err := DefinitionMapOf(ds)
  254. if err != nil {
  255. return err
  256. }
  257. definitions.SetForTest(tb, m, err)
  258. return nil
  259. }
  260. // DefinitionOf returns a setting definition by key,
  261. // or [ErrNoSuchKey] if the specified key does not exist,
  262. // or an error if there are conflicting policy definitions.
  263. func DefinitionOf(k pkey.Key) (*Definition, error) {
  264. ds, err := settingDefinitions()
  265. if err != nil {
  266. return nil, err
  267. }
  268. if d, ok := ds[k]; ok {
  269. return d, nil
  270. }
  271. return nil, ErrNoSuchKey
  272. }
  273. // Definitions returns all registered setting definitions,
  274. // or an error if different policies were registered under the same name.
  275. func Definitions() ([]*Definition, error) {
  276. ds, err := settingDefinitions()
  277. if err != nil {
  278. return nil, err
  279. }
  280. res := make([]*Definition, 0, len(ds))
  281. for _, d := range ds {
  282. res = append(res, d)
  283. }
  284. return res, nil
  285. }
  286. // PlatformList is a list of OSes.
  287. // An empty list indicates that all possible platforms are supported.
  288. type PlatformList []string
  289. // Has reports whether l contains the target platform.
  290. func (ls PlatformList) Has(target string) bool {
  291. if len(ls) == 0 {
  292. return true
  293. }
  294. return slices.ContainsFunc(ls, func(os string) bool {
  295. return strings.EqualFold(os, target)
  296. })
  297. }
  298. // HasCurrent is like Has, but for the current platform.
  299. func (ls PlatformList) HasCurrent() bool {
  300. return ls.Has(internal.OS())
  301. }
  302. // mergeFrom merges l2 into l. Since an empty list indicates no platform restrictions,
  303. // if either l or l2 is empty, the merged result in l will also be empty.
  304. func (ls *PlatformList) mergeFrom(l2 PlatformList) {
  305. switch {
  306. case len(*ls) == 0:
  307. // No-op. An empty list indicates no platform restrictions.
  308. case len(l2) == 0:
  309. // Merging with an empty list results in an empty list.
  310. *ls = l2
  311. default:
  312. // Append, sort and dedup.
  313. *ls = append(*ls, l2...)
  314. slices.Sort(*ls)
  315. *ls = slices.Compact(*ls)
  316. }
  317. }