mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00
ipn/ipn{auth,server}: update ipnauth.Actor to carry a context
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>
This commit is contained in:
parent
5a082fccec
commit
e9e2bc5bd7
@ -4,9 +4,11 @@
|
||||
package ipnauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn"
|
||||
)
|
||||
|
||||
@ -32,6 +34,11 @@ type Actor interface {
|
||||
// a connected LocalAPI client. Otherwise, it returns a zero value and false.
|
||||
ClientID() (_ ClientID, ok bool)
|
||||
|
||||
// Context returns the context associated with the actor.
|
||||
// It carries additional information about the actor
|
||||
// and is canceled when the actor is done.
|
||||
Context() context.Context
|
||||
|
||||
// CheckProfileAccess checks whether the actor has the necessary access rights
|
||||
// to perform a given action on the specified Tailscale profile.
|
||||
// It returns an error if access is denied.
|
||||
@ -102,3 +109,27 @@ func (id ClientID) MarshalJSON() ([]byte, error) {
|
||||
func (id *ClientID) UnmarshalJSON(b []byte) error {
|
||||
return json.Unmarshal(b, &id.v)
|
||||
}
|
||||
|
||||
type actorWithRequestReason struct {
|
||||
Actor
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// WithRequestReason returns an [Actor] that wraps the given actor and
|
||||
// carries the specified request reason in its context.
|
||||
func WithRequestReason(actor Actor, requestReason string) Actor {
|
||||
ctx := apitype.RequestReasonKey.WithValue(actor.Context(), requestReason)
|
||||
return &actorWithRequestReason{Actor: actor, ctx: ctx}
|
||||
}
|
||||
|
||||
// Context implements [Actor].
|
||||
func (a *actorWithRequestReason) Context() context.Context { return a.ctx }
|
||||
|
||||
type withoutCloseActor struct{ Actor }
|
||||
|
||||
// WithoutClose returns an [Actor] that does not expose the [ActorCloser] interface.
|
||||
// In other words, _, ok := WithoutClose(actor).(ActorCloser) will always be false,
|
||||
// even if the original actor implements [ActorCloser].
|
||||
func WithoutClose(actor Actor) Actor {
|
||||
return withoutCloseActor{actor}
|
||||
}
|
||||
|
@ -7,10 +7,36 @@ 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
|
||||
|
@ -4,6 +4,8 @@
|
||||
package ipnauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
)
|
||||
|
||||
@ -17,18 +19,21 @@ var Self Actor = unrestricted{}
|
||||
type unrestricted struct{}
|
||||
|
||||
// UserID implements [Actor].
|
||||
func (u unrestricted) UserID() ipn.WindowsUserID { return "" }
|
||||
func (unrestricted) UserID() ipn.WindowsUserID { return "" }
|
||||
|
||||
// Username implements [Actor].
|
||||
func (u unrestricted) Username() (string, error) { return "", nil }
|
||||
func (unrestricted) Username() (string, error) { return "", nil }
|
||||
|
||||
// Context implements [Actor].
|
||||
func (unrestricted) Context() context.Context { return context.Background() }
|
||||
|
||||
// ClientID implements [Actor].
|
||||
// It always returns (NoClientID, false) because the tailscaled itself
|
||||
// is not a connected LocalAPI client.
|
||||
func (u unrestricted) ClientID() (_ ClientID, ok bool) { return NoClientID, false }
|
||||
func (unrestricted) ClientID() (_ ClientID, ok bool) { return NoClientID, false }
|
||||
|
||||
// CheckProfileAccess implements [Actor].
|
||||
func (u unrestricted) CheckProfileAccess(_ ipn.LoginProfileView, _ ProfileAccess, _ AuditLogFunc) error {
|
||||
func (unrestricted) CheckProfileAccess(_ ipn.LoginProfileView, _ ProfileAccess, _ AuditLogFunc) error {
|
||||
// Unrestricted access to all profiles.
|
||||
return nil
|
||||
}
|
||||
@ -37,10 +42,10 @@ func (u unrestricted) CheckProfileAccess(_ ipn.LoginProfileView, _ ProfileAccess
|
||||
//
|
||||
// Deprecated: this method exists for compatibility with the current (as of 2025-01-28)
|
||||
// permission model and will be removed as we progress on tailscale/corp#18342.
|
||||
func (u unrestricted) IsLocalSystem() bool { return false }
|
||||
func (unrestricted) IsLocalSystem() bool { return false }
|
||||
|
||||
// IsLocalAdmin implements [Actor].
|
||||
//
|
||||
// Deprecated: this method exists for compatibility with the current (as of 2025-01-28)
|
||||
// permission model and will be removed as we progress on tailscale/corp#18342.
|
||||
func (u unrestricted) IsLocalAdmin(operatorUID string) bool { return false }
|
||||
func (unrestricted) IsLocalAdmin(operatorUID string) bool { return false }
|
||||
|
@ -4,6 +4,8 @@
|
||||
package ipnauth
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
@ -17,6 +19,7 @@ type TestActor struct {
|
||||
Name string // username associated with the actor, or ""
|
||||
NameErr error // error to be returned by [TestActor.Username]
|
||||
CID ClientID // non-zero if the actor represents a connected LocalAPI client
|
||||
Ctx context.Context // context associated with the actor
|
||||
LocalSystem bool // whether the actor represents the special Local System account on Windows
|
||||
LocalAdmin bool // whether the actor has local admin access
|
||||
}
|
||||
@ -30,6 +33,9 @@ func (a *TestActor) Username() (string, error) { return a.Name, a.NameErr }
|
||||
// ClientID implements [Actor].
|
||||
func (a *TestActor) ClientID() (_ ClientID, ok bool) { return a.CID, a.CID != NoClientID }
|
||||
|
||||
// Context implements [Actor].
|
||||
func (a *TestActor) Context() context.Context { return cmp.Or(a.Ctx, context.Background()) }
|
||||
|
||||
// CheckProfileAccess implements [Actor].
|
||||
func (a *TestActor) CheckProfileAccess(profile ipn.LoginProfileView, _ ProfileAccess, _ AuditLogFunc) error {
|
||||
return errors.New("profile access denied")
|
||||
|
@ -118,6 +118,9 @@ func (a *actor) ClientID() (_ ipnauth.ClientID, ok bool) {
|
||||
return a.clientID, a.clientID != ipnauth.NoClientID
|
||||
}
|
||||
|
||||
// Context implements [ipnauth.Actor].
|
||||
func (a *actor) Context() context.Context { return context.Background() }
|
||||
|
||||
// Username implements [ipnauth.Actor].
|
||||
func (a *actor) Username() (string, error) {
|
||||
if a.ci == nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user