policy.go 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package ipnauth
  4. import (
  5. "errors"
  6. "fmt"
  7. "tailscale.com/client/tailscale/apitype"
  8. "tailscale.com/feature/buildfeatures"
  9. "tailscale.com/ipn"
  10. "tailscale.com/tailcfg"
  11. "tailscale.com/util/syspolicy/pkey"
  12. "tailscale.com/util/syspolicy/policyclient"
  13. )
  14. type actorWithPolicyChecks struct{ Actor }
  15. // WithPolicyChecks returns an [Actor] that wraps the given actor and
  16. // performs additional policy checks on top of the access checks
  17. // implemented by the wrapped actor.
  18. func WithPolicyChecks(actor Actor) Actor {
  19. // TODO(nickkhyl): We should probably exclude the Windows Local System
  20. // account from policy checks as well.
  21. switch actor.(type) {
  22. case unrestricted:
  23. return actor
  24. default:
  25. return &actorWithPolicyChecks{Actor: actor}
  26. }
  27. }
  28. // CheckProfileAccess implements [Actor].
  29. func (a actorWithPolicyChecks) CheckProfileAccess(profile ipn.LoginProfileView, requestedAccess ProfileAccess, auditLogger AuditLogFunc) error {
  30. if err := a.Actor.CheckProfileAccess(profile, requestedAccess, auditLogger); err != nil {
  31. return err
  32. }
  33. requestReason := apitype.RequestReasonKey.Value(a.Context())
  34. return CheckDisconnectPolicy(a.Actor, profile, requestReason, auditLogger)
  35. }
  36. // CheckDisconnectPolicy checks if the policy allows the specified actor to disconnect
  37. // Tailscale with the given optional reason. It returns nil if the operation is allowed,
  38. // or an error if it is not. If auditLogger is non-nil, it is called to log the action
  39. // when required by the policy.
  40. //
  41. // Note: this function only checks the policy and does not check whether the actor has
  42. // the necessary access rights to the device or profile. It is intended to be used by
  43. // [Actor] implementations on platforms where [syspolicy] is supported.
  44. //
  45. // TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver]
  46. // and corp to this package.
  47. func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditFn AuditLogFunc) error {
  48. if !buildfeatures.HasSystemPolicy {
  49. return nil
  50. }
  51. if alwaysOn, _ := policyclient.Get().GetBoolean(pkey.AlwaysOn, false); !alwaysOn {
  52. return nil
  53. }
  54. if allowWithReason, _ := policyclient.Get().GetBoolean(pkey.AlwaysOnOverrideWithReason, false); !allowWithReason {
  55. return errors.New("disconnect not allowed: always-on mode is enabled")
  56. }
  57. if reason == "" {
  58. return errors.New("disconnect not allowed: reason required")
  59. }
  60. if auditFn != nil {
  61. var details string
  62. if username, _ := actor.Username(); username != "" { // best-effort; we don't have it on all platforms
  63. details = fmt.Sprintf("%q is being disconnected by %q: %v", profile.Name(), username, reason)
  64. } else {
  65. details = fmt.Sprintf("%q is being disconnected: %v", profile.Name(), reason)
  66. }
  67. if err := auditFn(tailcfg.AuditNodeDisconnect, details); err != nil {
  68. return err
  69. }
  70. }
  71. return nil
  72. }