mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-28 19:27:41 +00:00

The context carries additional information about the actor, such as the request reason, and is canceled when the actor is done. Additionally, we implement three new ipn.Actor types that wrap other actors to modify their behavior: - WithRequestReason, which adds a request reason to the actor; - WithoutClose, which narrows the actor's interface to prevent it from being closed; - WithPolicyChecks, which adds policy checks to the actor's CheckProfileAccess method. Updates #14823 Signed-off-by: Nick Khyl <nickk@tailscale.com>
73 lines
2.7 KiB
Go
73 lines
2.7 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package ipnauth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"tailscale.com/client/tailscale/apitype"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/util/syspolicy"
|
|
)
|
|
|
|
type actorWithPolicyChecks struct{ Actor }
|
|
|
|
// WithPolicyChecks returns an [Actor] that wraps the given actor and
|
|
// performs additional policy checks on top of the access checks
|
|
// implemented by the wrapped actor.
|
|
func WithPolicyChecks(actor Actor) Actor {
|
|
// TODO(nickkhyl): We should probably exclude the Windows Local System
|
|
// account from policy checks as well.
|
|
switch actor.(type) {
|
|
case unrestricted:
|
|
return actor
|
|
default:
|
|
return &actorWithPolicyChecks{Actor: actor}
|
|
}
|
|
}
|
|
|
|
// CheckProfileAccess implements [Actor].
|
|
func (a actorWithPolicyChecks) CheckProfileAccess(profile ipn.LoginProfileView, requestedAccess ProfileAccess, auditLogger AuditLogFunc) error {
|
|
if err := a.Actor.CheckProfileAccess(profile, requestedAccess, auditLogger); err != nil {
|
|
return err
|
|
}
|
|
requestReason := apitype.RequestReasonKey.Value(a.Context())
|
|
return CheckDisconnectPolicy(a.Actor, profile, requestReason, auditLogger)
|
|
}
|
|
|
|
// CheckDisconnectPolicy checks if the policy allows the specified actor to disconnect
|
|
// Tailscale with the given optional reason. It returns nil if the operation is allowed,
|
|
// or an error if it is not. If auditLogger is non-nil, it is called to log the action
|
|
// when required by the policy.
|
|
//
|
|
// Note: this function only checks the policy and does not check whether the actor has
|
|
// the necessary access rights to the device or profile. It is intended to be used by
|
|
// [Actor] implementations on platforms where [syspolicy] is supported.
|
|
//
|
|
// TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver]
|
|
// and corp to this package.
|
|
func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditLogger AuditLogFunc) error {
|
|
if alwaysOn, _ := syspolicy.GetBoolean(syspolicy.AlwaysOn, false); !alwaysOn {
|
|
return nil
|
|
}
|
|
if allowWithReason, _ := syspolicy.GetBoolean(syspolicy.AlwaysOnOverrideWithReason, false); !allowWithReason {
|
|
return errors.New("disconnect not allowed: always-on mode is enabled")
|
|
}
|
|
if reason == "" {
|
|
return errors.New("disconnect not allowed: reason required")
|
|
}
|
|
if auditLogger != nil {
|
|
var details string
|
|
if username, _ := actor.Username(); username != "" { // best-effort; we don't have it on all platforms
|
|
details = fmt.Sprintf("%q is being disconnected by %q: %v", profile.Name(), username, reason)
|
|
} else {
|
|
details = fmt.Sprintf("%q is being disconnected: %v", profile.Name(), reason)
|
|
}
|
|
// TODO(nickkhyl,barnstar): use a const for DISCONNECT_NODE.
|
|
auditLogger("DISCONNECT_NODE", details)
|
|
}
|
|
return nil
|
|
}
|