syspolicy.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package syspolicy contains the implementation of system policy management.
  4. // Calling code should use the client interface in
  5. // tailscale.com/util/syspolicy/policyclient.
  6. package syspolicy
  7. import (
  8. "errors"
  9. "fmt"
  10. "reflect"
  11. "time"
  12. "tailscale.com/util/syspolicy/internal/loggerx"
  13. "tailscale.com/util/syspolicy/pkey"
  14. "tailscale.com/util/syspolicy/policyclient"
  15. "tailscale.com/util/syspolicy/ptype"
  16. "tailscale.com/util/syspolicy/rsop"
  17. "tailscale.com/util/syspolicy/setting"
  18. "tailscale.com/util/syspolicy/source"
  19. )
  20. var (
  21. // ErrNotConfigured is returned when the requested policy setting is not configured.
  22. ErrNotConfigured = setting.ErrNotConfigured
  23. // ErrTypeMismatch is returned when there's a type mismatch between the actual type
  24. // of the setting value and the expected type.
  25. ErrTypeMismatch = setting.ErrTypeMismatch
  26. // ErrNoSuchKey is returned by [setting.DefinitionOf] when no policy setting
  27. // has been registered with the specified key.
  28. //
  29. // This error is also returned by a (now deprecated) [Handler] when the specified
  30. // key does not have a value set. While the package maintains compatibility with this
  31. // usage of ErrNoSuchKey, it is recommended to return [ErrNotConfigured] from newer
  32. // [source.Store] implementations.
  33. ErrNoSuchKey = setting.ErrNoSuchKey
  34. )
  35. // RegisterStore registers a new policy [source.Store] with the specified name and [setting.PolicyScope].
  36. //
  37. // It is a shorthand for [rsop.RegisterStore].
  38. func RegisterStore(name string, scope setting.PolicyScope, store source.Store) (*rsop.StoreRegistration, error) {
  39. return rsop.RegisterStore(name, scope, store)
  40. }
  41. // hasAnyOf returns whether at least one of the specified policy settings is configured,
  42. // or an error if no keys are provided or the check fails.
  43. func hasAnyOf(keys ...pkey.Key) (bool, error) {
  44. if len(keys) == 0 {
  45. return false, errors.New("at least one key must be specified")
  46. }
  47. policy, err := rsop.PolicyFor(setting.DefaultScope())
  48. if err != nil {
  49. return false, err
  50. }
  51. effective := policy.Get()
  52. for _, k := range keys {
  53. _, err := effective.GetErr(k)
  54. if errors.Is(err, setting.ErrNotConfigured) || errors.Is(err, setting.ErrNoSuchKey) {
  55. continue
  56. }
  57. if err != nil {
  58. return false, err
  59. }
  60. return true, nil
  61. }
  62. return false, nil
  63. }
  64. // getString returns a string policy setting with the specified key,
  65. // or defaultValue if it does not exist.
  66. func getString(key pkey.Key, defaultValue string) (string, error) {
  67. return getCurrentPolicySettingValue(key, defaultValue)
  68. }
  69. // getUint64 returns a numeric policy setting with the specified key,
  70. // or defaultValue if it does not exist.
  71. func getUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
  72. return getCurrentPolicySettingValue(key, defaultValue)
  73. }
  74. // getBoolean returns a boolean policy setting with the specified key,
  75. // or defaultValue if it does not exist.
  76. func getBoolean(key pkey.Key, defaultValue bool) (bool, error) {
  77. return getCurrentPolicySettingValue(key, defaultValue)
  78. }
  79. // getStringArray returns a multi-string policy setting with the specified key,
  80. // or defaultValue if it does not exist.
  81. func getStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
  82. return getCurrentPolicySettingValue(key, defaultValue)
  83. }
  84. // getPreferenceOption loads a policy from the registry that can be
  85. // managed by an enterprise policy management system and allows administrative
  86. // overrides of users' choices in a way that we do not want tailcontrol to have
  87. // the authority to set. It describes user-decides/always/never options, where
  88. // "always" and "never" remove the user's ability to make a selection. If not
  89. // present or set to a different value, defaultValue (and a nil error) is returned.
  90. func getPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
  91. return getCurrentPolicySettingValue(name, defaultValue)
  92. }
  93. // getVisibility loads a policy from the registry that can be managed
  94. // by an enterprise policy management system and describes show/hide decisions
  95. // for UI elements. The registry value should be a string set to "show" (return
  96. // true) or "hide" (return true). If not present or set to a different value,
  97. // "show" (return false) is the default.
  98. func getVisibility(name pkey.Key) (ptype.Visibility, error) {
  99. return getCurrentPolicySettingValue(name, ptype.VisibleByPolicy)
  100. }
  101. // getDuration loads a policy from the registry that can be managed
  102. // by an enterprise policy management system and describes a duration for some
  103. // action. The registry value should be a string that time.ParseDuration
  104. // understands. If the registry value is "" or can not be processed,
  105. // defaultValue is returned instead.
  106. func getDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
  107. d, err := getCurrentPolicySettingValue(name, defaultValue)
  108. if err != nil {
  109. return d, err
  110. }
  111. if d < 0 {
  112. return defaultValue, nil
  113. }
  114. return d, nil
  115. }
  116. // registerChangeCallback adds a function that will be called whenever the effective policy
  117. // for the default scope changes. The returned function can be used to unregister the callback.
  118. func registerChangeCallback(cb rsop.PolicyChangeCallback) (unregister func(), err error) {
  119. effective, err := rsop.PolicyFor(setting.DefaultScope())
  120. if err != nil {
  121. return nil, err
  122. }
  123. return effective.RegisterChangeCallback(cb), nil
  124. }
  125. // getCurrentPolicySettingValue returns the value of the policy setting
  126. // specified by its key from the [rsop.Policy] of the [setting.DefaultScope]. It
  127. // returns def if the policy setting is not configured, or an error if it has
  128. // an error or could not be converted to the specified type T.
  129. func getCurrentPolicySettingValue[T setting.ValueType](key pkey.Key, def T) (T, error) {
  130. effective, err := rsop.PolicyFor(setting.DefaultScope())
  131. if err != nil {
  132. return def, err
  133. }
  134. value, err := effective.Get().GetErr(key)
  135. if err != nil {
  136. if errors.Is(err, setting.ErrNotConfigured) || errors.Is(err, setting.ErrNoSuchKey) {
  137. return def, nil
  138. }
  139. return def, err
  140. }
  141. if res, ok := value.(T); ok {
  142. return res, nil
  143. }
  144. return convertPolicySettingValueTo(value, def)
  145. }
  146. func convertPolicySettingValueTo[T setting.ValueType](value any, def T) (T, error) {
  147. // Convert [PreferenceOption], [Visibility], or [time.Duration] back to a string
  148. // if someone requests a string instead of the actual setting's value.
  149. // TODO(nickkhyl): check if this behavior is relied upon anywhere besides the old tests.
  150. if reflect.TypeFor[T]().Kind() == reflect.String {
  151. if str, ok := value.(fmt.Stringer); ok {
  152. return any(str.String()).(T), nil
  153. }
  154. }
  155. return def, fmt.Errorf("%w: got %T, want %T", setting.ErrTypeMismatch, value, def)
  156. }
  157. // SelectControlURL returns the ControlURL to use based on a value in
  158. // the registry (LoginURL) and the one on disk (in the GUI's
  159. // prefs.conf). If both are empty, it returns a default value. (It
  160. // always return a non-empty value)
  161. //
  162. // See https://github.com/tailscale/tailscale/issues/2798 for some background.
  163. func SelectControlURL(reg, disk string) string {
  164. const def = "https://controlplane.tailscale.com"
  165. // Prior to Dec 2020's commit 739b02e6, the installer
  166. // wrote a LoginURL value of https://login.tailscale.com to the registry.
  167. const oldRegDef = "https://login.tailscale.com"
  168. // If they have an explicit value in the registry, use it,
  169. // unless it's an old default value from an old installer.
  170. // Then we have to see which is better.
  171. if reg != "" {
  172. if reg != oldRegDef {
  173. // Something explicit in the registry that we didn't
  174. // set ourselves by the installer.
  175. return reg
  176. }
  177. if disk == "" {
  178. // Something in the registry is better than nothing on disk.
  179. return reg
  180. }
  181. if disk != def && disk != oldRegDef {
  182. // The value in the registry is the old
  183. // default (login.tailscale.com) but the value
  184. // on disk is neither our old nor new default
  185. // value, so it must be some custom thing that
  186. // the user cares about. Prefer the disk value.
  187. return disk
  188. }
  189. }
  190. if disk != "" {
  191. return disk
  192. }
  193. return def
  194. }
  195. func init() {
  196. policyclient.RegisterClientImpl(globalSyspolicy{})
  197. }
  198. // globalSyspolicy implements [policyclient.Client] using the syspolicy global
  199. // functions and global registrations.
  200. //
  201. // TODO: de-global-ify. This implementation using the old global functions
  202. // is an intermediate stage while changing policyclient to be modular.
  203. type globalSyspolicy struct{}
  204. func (globalSyspolicy) GetBoolean(key pkey.Key, defaultValue bool) (bool, error) {
  205. return getBoolean(key, defaultValue)
  206. }
  207. func (globalSyspolicy) GetString(key pkey.Key, defaultValue string) (string, error) {
  208. return getString(key, defaultValue)
  209. }
  210. func (globalSyspolicy) GetStringArray(key pkey.Key, defaultValue []string) ([]string, error) {
  211. return getStringArray(key, defaultValue)
  212. }
  213. func (globalSyspolicy) SetDebugLoggingEnabled(enabled bool) {
  214. loggerx.SetDebugLoggingEnabled(enabled)
  215. }
  216. func (globalSyspolicy) GetUint64(key pkey.Key, defaultValue uint64) (uint64, error) {
  217. return getUint64(key, defaultValue)
  218. }
  219. func (globalSyspolicy) GetDuration(name pkey.Key, defaultValue time.Duration) (time.Duration, error) {
  220. return getDuration(name, defaultValue)
  221. }
  222. func (globalSyspolicy) GetPreferenceOption(name pkey.Key, defaultValue ptype.PreferenceOption) (ptype.PreferenceOption, error) {
  223. return getPreferenceOption(name, defaultValue)
  224. }
  225. func (globalSyspolicy) GetVisibility(name pkey.Key) (ptype.Visibility, error) {
  226. return getVisibility(name)
  227. }
  228. func (globalSyspolicy) HasAnyOf(keys ...pkey.Key) (bool, error) {
  229. return hasAnyOf(keys...)
  230. }
  231. func (globalSyspolicy) RegisterChangeCallback(cb func(policyclient.PolicyChange)) (unregister func(), err error) {
  232. return registerChangeCallback(cb)
  233. }