From 5eacf6184466c42406f9edb9ff317c349b50746e Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Fri, 7 Feb 2025 14:01:52 -0600 Subject: [PATCH] ipn/ipnauth: implement WindowsActor WindowsActor is an ipnauth.Actor implementation that represents a logged-in Windows user by wrapping their Windows user token. Updates #14823 Signed-off-by: Nick Khyl --- ipn/ipnauth/actor_windows.go | 102 +++++++++++++++++++++++++++++++++ ipn/ipnauth/ipnauth_windows.go | 10 +++- 2 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 ipn/ipnauth/actor_windows.go diff --git a/ipn/ipnauth/actor_windows.go b/ipn/ipnauth/actor_windows.go new file mode 100644 index 000000000..90d3bdd36 --- /dev/null +++ b/ipn/ipnauth/actor_windows.go @@ -0,0 +1,102 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package ipnauth + +import ( + "context" + "errors" + + "golang.org/x/sys/windows" + "tailscale.com/ipn" + "tailscale.com/types/lazy" +) + +// WindowsActor implements [Actor]. +var _ Actor = (*WindowsActor)(nil) + +// WindowsActor represents a logged in Windows user. +type WindowsActor struct { + ctx context.Context + cancelCtx context.CancelFunc + token WindowsToken + uid ipn.WindowsUserID + username lazy.SyncValue[string] +} + +// NewWindowsActorWithToken returns a new [WindowsActor] for the user +// represented by the given [windows.Token]. +// It takes ownership of the token. +func NewWindowsActorWithToken(t windows.Token) (_ *WindowsActor, err error) { + tok := newToken(t) + uid, err := tok.UID() + if err != nil { + t.Close() + return nil, err + } + ctx, cancelCtx := context.WithCancel(context.Background()) + return &WindowsActor{ctx: ctx, cancelCtx: cancelCtx, token: tok, uid: uid}, nil +} + +// UserID implements [Actor]. +func (a *WindowsActor) UserID() ipn.WindowsUserID { + return a.uid +} + +// Username implements [Actor]. +func (a *WindowsActor) Username() (string, error) { + return a.username.GetErr(a.token.Username) +} + +// ClientID implements [Actor]. +func (a *WindowsActor) ClientID() (_ ClientID, ok bool) { + // TODO(nickkhyl): assign and return a client ID when the actor + // represents a connected LocalAPI client. + return NoClientID, false +} + +// Context implements [Actor]. +func (a *WindowsActor) Context() context.Context { + return a.ctx +} + +// CheckProfileAccess implements [Actor]. +func (a *WindowsActor) CheckProfileAccess(profile ipn.LoginProfileView, _ ProfileAccess, _ AuditLogFunc) error { + if profile.LocalUserID() != a.UserID() { + // TODO(nickkhyl): return errors of more specific types and have them + // translated to the appropriate HTTP status codes in the API handler. + return errors.New("the target profile does not belong to the user") + } + return nil +} + +// IsLocalSystem implements [Actor]. +// +// Deprecated: this method exists for compatibility with the current (as of 2025-02-06) +// permission model and will be removed as we progress on tailscale/corp#18342. +func (a *WindowsActor) IsLocalSystem() bool { + // https://web.archive.org/web/2024/https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + const systemUID = ipn.WindowsUserID("S-1-5-18") + return a.uid == systemUID +} + +// IsLocalAdmin implements [Actor]. +// +// Deprecated: this method exists for compatibility with the current (as of 2025-02-06) +// permission model and will be removed as we progress on tailscale/corp#18342. +func (a *WindowsActor) IsLocalAdmin(operatorUID string) bool { + return a.token.IsElevated() +} + +// Close releases resources associated with the actor +// and cancels its context. +func (a *WindowsActor) Close() error { + if a.token != nil { + if err := a.token.Close(); err != nil { + return err + } + a.token = nil + } + a.cancelCtx() + return nil +} diff --git a/ipn/ipnauth/ipnauth_windows.go b/ipn/ipnauth/ipnauth_windows.go index 9abd04cd1..1138bc23d 100644 --- a/ipn/ipnauth/ipnauth_windows.go +++ b/ipn/ipnauth/ipnauth_windows.go @@ -36,6 +36,12 @@ type token struct { t windows.Token } +func newToken(t windows.Token) *token { + tok := &token{t: t} + runtime.SetFinalizer(tok, func(t *token) { t.Close() }) + return tok +} + func (t *token) UID() (ipn.WindowsUserID, error) { sid, err := t.uid() if err != nil { @@ -184,7 +190,5 @@ func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) { return nil, err } - result := &token{t: windows.Token(h)} - runtime.SetFinalizer(result, func(t *token) { t.Close() }) - return result, nil + return newToken(windows.Token(h)), nil }