lanscaping: drop more ipnauth

-rwxr-xr-x@ 1 bradfitz  staff  9871042 Jan 11 14:21 /Users/bradfitz/bin/tailscaled.min
-rwxr-xr-x@ 1 bradfitz  staff  9765016 Jan 11 14:21 /Users/bradfitz/bin/tailscaled.minlinux

Change-Id: I558009a377d699e51c585f8f397e1580434bdddd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2025-01-11 14:21:31 -08:00
parent c781951fdd
commit b9745a5375
5 changed files with 33 additions and 459 deletions

View File

@ -38,7 +38,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/internal/noiseconn from tailscale.com/control/controlclient
tailscale.com/ipn from tailscale.com/cmd/tailscaled+
tailscale.com/ipn/conffile from tailscale.com/cmd/tailscaled+
tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+
tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+
@ -89,7 +88,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/types/views from tailscale.com/control/controlclient+
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
tailscale.com/util/ctxkey from tailscale.com/ipn/ipnserver+
tailscale.com/util/ctxkey from tailscale.com/types/logger
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
tailscale.com/util/dnsname from tailscale.com/hostinfo+
tailscale.com/util/execqueue from tailscale.com/control/controlclient
@ -113,7 +112,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/testenv from tailscale.com/control/controlclient+
tailscale.com/util/uniq from tailscale.com/ipn/ipnlocal+
tailscale.com/util/vizerror from tailscale.com/tailcfg+
tailscale.com/util/winutil from tailscale.com/ipn/ipnauth
tailscale.com/util/zstdframe from tailscale.com/control/controlclient
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/cmd/tailscaled+
@ -228,7 +226,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
os from crypto/rand+
os/exec from tailscale.com/cmd/tailscaled+
os/signal from tailscale.com/cmd/tailscaled
os/user from tailscale.com/ipn/ipnauth+
os/user from tailscale.com/ipn/ipnserver
path from io/fs+
path/filepath from crypto/x509+
reflect from crypto/x509+

View File

@ -42,7 +42,6 @@ import (
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/ipn/conffile"
"tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/ipset"
"tailscale.com/net/netcheck"
@ -88,6 +87,8 @@ import (
"tailscale.com/wgengine/wgcfg/nmcfg"
)
type ipnauthActor interface{}
var controlDebugFlags = getControlDebugFlags()
func getControlDebugFlags() []string {
@ -124,11 +125,11 @@ func RegisterNewSSHServer(fn newSSHServerFunc) {
}
// watchSession represents a WatchNotifications channel,
// an [ipnauth.Actor] that owns it (e.g., a connected GUI/CLI),
// an [ipnauthActor] that owns it (e.g., a connected GUI/CLI),
// and sessionID as required to close targeted buses.
type watchSession struct {
ch chan *ipn.Notify
owner ipnauth.Actor // or nil
owner ipnauthActor // or nil
sessionID string
cancel context.CancelFunc // to shut down the session
}
@ -235,9 +236,9 @@ type LocalBackend struct {
endpoints []tailcfg.Endpoint
blocked bool
keyExpired bool
authURL string // non-empty if not Running
authURLTime time.Time // when the authURL was received from the control server
authActor ipnauth.Actor // an actor who called [LocalBackend.StartLoginInteractive] last, or nil
authURL string // non-empty if not Running
authURLTime time.Time // when the authURL was received from the control server
authActor ipnauthActor // an actor who called [LocalBackend.StartLoginInteractive] last, or nil
egg bool
prevIfState *netmon.State
loginFlags controlclient.LoginFlags
@ -260,7 +261,7 @@ type LocalBackend struct {
componentLogUntil map[string]componentLogState
// c2nUpdateStatus is the status of c2n-triggered client update.
c2nUpdateStatus updateStatus
currentUser ipnauth.Actor
currentUser ipnauthActor
selfUpdateProgress []ipnstate.UpdateProgress
lastSelfUpdateState ipnstate.SelfUpdateStatus
// capForcedNetfilter is the netfilter that control instructs Linux clients
@ -2249,10 +2250,10 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa
b.WatchNotificationsAs(ctx, nil, mask, onWatchAdded, fn)
}
// WatchNotificationsAs is like WatchNotifications but takes an [ipnauth.Actor]
// WatchNotificationsAs is like WatchNotifications but takes an [ipnauthActor]
// as an additional parameter. If non-nil, the specified callback is invoked
// only for notifications relevant to this actor.
func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.Actor, mask ipn.NotifyWatchOpt, onWatchAdded func(), fn func(roNotify *ipn.Notify) (keepGoing bool)) {
func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauthActor, mask ipn.NotifyWatchOpt, onWatchAdded func(), fn func(roNotify *ipn.Notify) (keepGoing bool)) {
ch := make(chan *ipn.Notify, 128)
sessionID := rands.HexString(16)
origFn := fn
@ -2442,10 +2443,6 @@ type notificationTarget struct {
// TODO(nickkhyl): make this field cross-platform rather
// than Windows-specific.
userID ipn.WindowsUserID
// clientID identifies a client that should be the exclusive recipient
// of the notification. A zero value indicates that notification should
// be sent to all sessions of the specified user.
clientID ipnauth.ClientID
}
var allClients = notificationTarget{} // broadcast to all connected clients
@ -2454,11 +2451,10 @@ var allClients = notificationTarget{} // broadcast to all connected clients
// representing the same user as the specified actor. If the actor represents
// a specific connected client, the [ipnauth.ClientID] must also match.
// If the actor is nil, the [notificationTarget] matches all actors.
func toNotificationTarget(actor ipnauth.Actor) notificationTarget {
func toNotificationTarget(actor ipnauthActor) notificationTarget {
t := notificationTarget{}
if actor != nil {
t.userID = actor.UserID()
t.clientID, _ = actor.ClientID()
// lanscaping
}
return t
}
@ -2466,22 +2462,8 @@ func toNotificationTarget(actor ipnauth.Actor) notificationTarget {
// match reports whether the specified actor should receive notifications
// targeting t. If the actor is nil, it should only receive notifications
// intended for all users.
func (t notificationTarget) match(actor ipnauth.Actor) bool {
if t == allClients {
return true
}
if actor == nil {
return false
}
if t.userID != "" && t.userID != actor.UserID() {
return false
}
if t.clientID != ipnauth.NoClientID {
clientID, ok := actor.ClientID()
if !ok || clientID != t.clientID {
return false
}
}
func (t notificationTarget) match(actor ipnauthActor) bool {
return true
}
@ -2520,7 +2502,7 @@ func (b *LocalBackend) sendToLocked(n ipn.Notify, recipient notificationTarget)
// If url is "", it is equivalent to calling [LocalBackend.resetAuthURLLocked] with b.mu held.
func (b *LocalBackend) setAuthURL(url string) {
var popBrowser, keyExpired bool
var recipient ipnauth.Actor
var recipient ipnauthActor
b.mu.Lock()
switch {
@ -2555,7 +2537,7 @@ func (b *LocalBackend) setAuthURL(url string) {
// keyExpired is the value of b.keyExpired upon entry and indicates
// whether the node's key has expired.
// It must not be called with b.mu held.
func (b *LocalBackend) popBrowserAuthNow(url string, keyExpired bool, recipient ipnauth.Actor) {
func (b *LocalBackend) popBrowserAuthNow(url string, keyExpired bool, recipient ipnauthActor) {
b.logf("popBrowserAuthNow(%q): url=%v, key-expired=%v, seamless-key-renewal=%v", maybeUsernameOf(recipient), url != "", keyExpired, b.seamlessRenewalEnabled())
// Deconfigure the local network data plane if:
@ -2836,45 +2818,10 @@ func (b *LocalBackend) InServerMode() bool {
// Currently (as of 2024-08-26), this is only used on Windows.
// We plan to remove it as part of the multi-user and unattended mode improvements
// as we progress on tailscale/corp#18342.
func (b *LocalBackend) CheckIPNConnectionAllowed(actor ipnauth.Actor) error {
b.mu.Lock()
defer b.mu.Unlock()
serverModeUid := b.pm.CurrentUserID()
if serverModeUid == "" {
// Either this platform isn't a "multi-user" platform or we're not yet
// running as one.
return nil
}
if !b.pm.CurrentPrefs().ForceDaemon() {
return nil
}
// Always allow Windows SYSTEM user to connect,
// even if Tailscale is currently being used by another user.
if actor.IsLocalSystem() {
return nil
}
uid := actor.UserID()
if uid == "" {
return errors.New("empty user uid in connection identity")
}
if uid != serverModeUid {
return fmt.Errorf("Tailscale running in server mode (%q); connection from %q not allowed", b.tryLookupUserName(string(serverModeUid)), b.tryLookupUserName(string(uid)))
}
func (b *LocalBackend) CheckIPNConnectionAllowed(actor ipnauthActor) error {
return nil
}
// tryLookupUserName tries to look up the username for the uid.
// It returns the username on success, or the UID on failure.
func (b *LocalBackend) tryLookupUserName(uid string) string {
u, err := ipnauth.LookupUserFromID(b.logf, uid)
if err != nil {
return uid
}
return u.Username
}
// StartLoginInteractive requests a new interactive login from controlclient,
// unless such a flow is already in progress, in which case
// StartLoginInteractive attempts to pick up the in-progress flow where it left
@ -2883,12 +2830,12 @@ func (b *LocalBackend) StartLoginInteractive(ctx context.Context) error {
return b.StartLoginInteractiveAs(ctx, nil)
}
// StartLoginInteractiveAs is like StartLoginInteractive but takes an [ipnauth.Actor]
// StartLoginInteractiveAs is like StartLoginInteractive but takes an [ipnauthActor]
// as an additional parameter. If non-nil, the specified user is expected to complete
// the interactive login, and therefore will receive the BrowseToURL notification once
// the control plane sends us one. Otherwise, the notification will be delivered to all
// active [watchSession]s.
func (b *LocalBackend) StartLoginInteractiveAs(ctx context.Context, user ipnauth.Actor) error {
func (b *LocalBackend) StartLoginInteractiveAs(ctx context.Context, user ipnauthActor) error {
b.mu.Lock()
if b.cc == nil {
panic("LocalBackend.assertClient: b.cc == nil")
@ -3037,46 +2984,6 @@ func (b *LocalBackend) shouldUploadServices() bool {
return !p.ShieldsUp() && b.netMap.CollectServices
}
// SetCurrentUser is used to implement support for multi-user systems (only
// Windows 2022-11-25). On such systems, the uid is used to determine which
// user's state should be used. The current user is maintained by active
// connections open to the backend.
//
// When the backend initially starts it will typically start with no user. Then,
// the first connection to the backend from the GUI frontend will set the
// current user. Once set, the current user cannot be changed until all previous
// connections are closed. The user is also used to determine which
// LoginProfiles are accessible.
//
// In unattended mode, the backend will start with the user which enabled
// unattended mode. The user must disable unattended mode before the user can be
// changed.
//
// On non-multi-user systems, the user should be set to nil.
//
// SetCurrentUser returns the ipn.WindowsUserID associated with the user
// when successful.
func (b *LocalBackend) SetCurrentUser(actor ipnauth.Actor) (ipn.WindowsUserID, error) {
var uid ipn.WindowsUserID
if actor != nil {
uid = actor.UserID()
}
unlock := b.lockAndGetUnlock()
defer unlock()
if b.pm.CurrentUserID() == uid {
return uid, nil
}
b.pm.SetCurrentUserID(uid)
if c, ok := b.currentUser.(ipnauth.ActorCloser); ok {
c.Close()
}
b.currentUser = actor
b.resetForProfileChangeLockedOnEntry(unlock)
return uid, nil
}
func (b *LocalBackend) CheckPrefs(p *ipn.Prefs) error {
b.mu.Lock()
defer b.mu.Unlock()
@ -3989,12 +3896,6 @@ func (b *LocalBackend) ResetForClientDisconnect() {
b.setNetMapLocked(nil)
b.pm.Reset()
if b.currentUser != nil {
if c, ok := b.currentUser.(ipnauth.ActorCloser); ok {
c.Close()
}
b.currentUser = nil
}
b.keyExpired = false
b.resetAuthURLLocked()
b.activeLogin = ""
@ -5149,12 +5050,8 @@ func (b *LocalBackend) srcIPHasCapForFilter(srcIP netip.Addr, cap tailcfg.NodeCa
// maybeUsernameOf returns the actor's username if the actor
// is non-nil and its username can be resolved.
func maybeUsernameOf(actor ipnauth.Actor) string {
var username string
if actor != nil {
username, _ = actor.Username()
}
return username
func maybeUsernameOf(actor ipnauthActor) string {
return "root"
}
// VIPServices returns the list of tailnet services that this node

View File

@ -1,176 +0,0 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package ipnserver
import (
"context"
"errors"
"fmt"
"net"
"runtime"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnauth"
"tailscale.com/types/logger"
"tailscale.com/util/ctxkey"
"tailscale.com/version"
)
var _ ipnauth.Actor = (*actor)(nil)
// actor implements [ipnauth.Actor] and provides additional functionality that is
// specific to the current (as of 2024-08-27) permission model.
//
// Deprecated: this type exists for compatibility reasons and will be removed as
// we progress on tailscale/corp#18342.
type actor struct {
logf logger.Logf
ci *ipnauth.ConnIdentity
clientID ipnauth.ClientID
isLocalSystem bool // whether the actor is the Windows' Local System identity.
}
func newActor(logf logger.Logf, c net.Conn) (*actor, error) {
ci, err := ipnauth.GetConnIdentity(logf, c)
if err != nil {
return nil, err
}
var clientID ipnauth.ClientID
if pid := ci.Pid(); pid != 0 {
// Derive [ipnauth.ClientID] from the PID of the connected client process.
// TODO(nickkhyl): This is transient and will be re-worked as we
// progress on tailscale/corp#18342. At minimum, we should use a 2-tuple
// (PID + StartTime) or a 3-tuple (PID + StartTime + UID) to identify
// the client process. This helps prevent security issues where a
// terminated client process's PID could be reused by a different
// process. This is not currently an issue as we allow only one user to
// connect anyway.
// Additionally, we should consider caching authentication results since
// operations like retrieving a username by SID might require network
// connectivity on domain-joined devices and/or be slow.
clientID = ipnauth.ClientIDFrom(pid)
}
return &actor{logf: logf, ci: ci, clientID: clientID, isLocalSystem: connIsLocalSystem(ci)}, nil
}
// IsLocalSystem implements [ipnauth.Actor].
func (a *actor) IsLocalSystem() bool {
return a.isLocalSystem
}
// IsLocalAdmin implements [ipnauth.Actor].
func (a *actor) IsLocalAdmin(operatorUID string) bool {
return a.isLocalSystem || connIsLocalAdmin(a.logf, a.ci, operatorUID)
}
// UserID implements [ipnauth.Actor].
func (a *actor) UserID() ipn.WindowsUserID {
return a.ci.WindowsUserID()
}
func (a *actor) pid() int {
return a.ci.Pid()
}
// ClientID implements [ipnauth.Actor].
func (a *actor) ClientID() (_ ipnauth.ClientID, ok bool) {
return a.clientID, a.clientID != ipnauth.NoClientID
}
// Username implements [ipnauth.Actor].
func (a *actor) Username() (string, error) {
if a.ci == nil {
a.logf("[unexpected] missing ConnIdentity in ipnserver.actor")
return "", errors.New("missing ConnIdentity")
}
switch runtime.GOOS {
case "windows":
tok, err := a.ci.WindowsToken()
if err != nil {
return "", fmt.Errorf("get windows token: %w", err)
}
defer tok.Close()
return tok.Username()
case "darwin", "linux", "illumos", "solaris":
return "root", nil
default:
return "", errors.New("unsupported OS")
}
}
type actorOrError struct {
actor *actor
err error
}
func (a actorOrError) unwrap() (*actor, error) {
return a.actor, a.err
}
var errNoActor = errors.New("connection actor not available")
var actorKey = ctxkey.New("ipnserver.actor", actorOrError{err: errNoActor})
// contextWithActor returns a new context that carries the identity of the actor
// owning the other end of the [net.Conn]. It can be retrieved with [actorFromContext].
func contextWithActor(ctx context.Context, logf logger.Logf, c net.Conn) context.Context {
actor, err := newActor(logf, c)
return actorKey.WithValue(ctx, actorOrError{actor: actor, err: err})
}
// actorFromContext returns an [actor] associated with ctx,
// or an error if the context does not carry an actor's identity.
func actorFromContext(ctx context.Context) (*actor, error) {
return actorKey.Value(ctx).unwrap()
}
func connIsLocalSystem(ci *ipnauth.ConnIdentity) bool {
token, err := ci.WindowsToken()
return err == nil && token.IsLocalSystem()
}
// connIsLocalAdmin reports whether the connected client has administrative
// access to the local machine, for whatever that means with respect to the
// current OS.
//
// This is useful because tailscaled itself always runs with elevated rights:
// we want to avoid privilege escalation for certain mutative operations.
func connIsLocalAdmin(logf logger.Logf, ci *ipnauth.ConnIdentity, operatorUID string) bool {
if ci == nil {
logf("[unexpected] missing ConnIdentity in LocalAPI Handler")
return false
}
switch runtime.GOOS {
case "windows":
tok, err := ci.WindowsToken()
if err != nil {
if !errors.Is(err, ipnauth.ErrNotImplemented) {
logf("ipnauth.ConnIdentity.WindowsToken() error: %v", err)
}
return false
}
defer tok.Close()
return tok.IsElevated()
case "darwin":
// Unknown, or at least unchecked on sandboxed macOS variants. Err on
// the side of less permissions.
//
// authorizeServeConfigForGOOSAndUserContext should not call
// connIsLocalAdmin on sandboxed variants anyway.
if version.IsSandboxedMacOS() {
return false
}
// This is a standalone tailscaled setup, use the same logic as on
// Linux.
fallthrough
case "linux":
return true
default:
return false
}
}

View File

@ -8,8 +8,6 @@ package ipnserver
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
@ -27,7 +25,6 @@ import (
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/mak"
"tailscale.com/util/set"
)
@ -49,9 +46,8 @@ type Server struct {
// lock order: mu, then LocalBackend.mu
mu sync.Mutex
lastUserID ipn.WindowsUserID // tracks last userid; on change, Reset state for paranoia
activeReqs map[*http.Request]*actor
backendWaiter waiterSet // of LocalBackend waiters
zeroReqWaiter waiterSet // of blockUntilZeroConnections waiters
backendWaiter waiterSet // of LocalBackend waiters
zeroReqWaiter waiterSet // of blockUntilZeroConnections waiters
}
func (s *Server) mustBackend() *ipnlocal.LocalBackend {
@ -170,21 +166,10 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
return
}
ci, err := actorFromContext(r.Context())
if err != nil {
if errors.Is(err, errNoActor) {
http.Error(w, "internal error: "+err.Error(), http.StatusInternalServerError)
} else {
http.Error(w, err.Error(), http.StatusUnauthorized)
}
return
}
onDone, err := s.addActiveHTTPRequest(r, ci)
onDone, err := s.addActiveHTTPRequest(r)
if err != nil {
if ou, ok := err.(inUseOtherUserError); ok && localapi.InUseOtherUserIPNStream(w, r, ou.Unwrap()) {
w.(http.Flusher).Flush()
s.blockWhileIdentityInUse(ctx, ci)
return
}
http.Error(w, err.Error(), http.StatusUnauthorized)
@ -194,8 +179,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/localapi/") {
lah := localapi.NewHandler(lb, s.logf, s.backendLogID)
lah.PermitRead, lah.PermitWrite = ci.Permissions(lb.OperatorUserID())
lah.Actor = ci
lah.PermitRead, lah.PermitWrite = true, true
lah.ServeHTTP(w, r)
return
}
@ -228,88 +212,11 @@ func (e inUseOtherUserError) Unwrap() error { return e.error }
// The returned error, when non-nil, will be of type inUseOtherUserError.
//
// s.mu must be held.
func (s *Server) checkConnIdentityLocked(ci *actor) error {
// If clients are already connected, verify they're the same user.
// This mostly matters on Windows at the moment.
if len(s.activeReqs) > 0 {
var active *actor
for _, active = range s.activeReqs {
break
}
if active != nil {
// Always allow Windows SYSTEM user to connect,
// even if Tailscale is currently being used by another user.
if ci.IsLocalSystem() {
return nil
}
func (s *Server) checkConnIdentityLocked() error {
if ci.UserID() != active.UserID() {
var b strings.Builder
b.WriteString("Tailscale already in use")
if username, err := active.Username(); err == nil {
fmt.Fprintf(&b, " by %s", username)
}
fmt.Fprintf(&b, ", pid %d", active.pid())
return inUseOtherUserError{errors.New(b.String())}
}
}
}
if err := s.mustBackend().CheckIPNConnectionAllowed(ci); err != nil {
return inUseOtherUserError{err}
}
return nil
}
// blockWhileIdentityInUse blocks while ci can't connect to the server because
// the server is in use by a different user.
//
// This is primarily used for the Windows GUI, to block until one user's done
// controlling the tailscaled process.
func (s *Server) blockWhileIdentityInUse(ctx context.Context, actor *actor) error {
inUse := func() bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.checkConnIdentityLocked(actor).(inUseOtherUserError)
return ok
}
for inUse() {
// Check whenever the connection count drops down to zero.
ready, cleanup := s.zeroReqWaiter.add(&s.mu, ctx)
<-ready
cleanup()
if err := ctx.Err(); err != nil {
return err
}
}
return nil
}
// Permissions returns the actor's permissions for accessing
// the Tailscale local daemon API. The operatorUID is only used on
// Unix-like platforms and specifies the ID of a local user
// (in the os/user.User.Uid string form) who is allowed
// to operate tailscaled without being root or using sudo.
func (a *actor) Permissions(operatorUID string) (read, write bool) {
switch envknob.GOOS() {
case "windows":
// As of 2024-08-27, according to the current permission model,
// Windows users always have read/write access to the local API if
// they're allowed to connect. Whether a user is allowed to connect
// is determined by [Server.checkConnIdentityLocked] when adding a
// new connection in [Server.addActiveHTTPRequest]. Therefore, it's
// acceptable to permit read and write access without any additional
// checks here. Note that this permission model is being changed in
// tailscale/corp#18342.
return true, true
case "js":
return true, true
}
if a.ci.IsUnixSock() {
return true, !a.ci.IsReadonlyConn(operatorUID, logger.Discard)
}
return false, false
}
// userIDFromString maps from either a numeric user id in string form
// ("998") or username ("caddy") to its string userid ("998").
// It returns the empty string on error.
@ -339,10 +246,7 @@ func isAllDigit(s string) bool {
// The returned error may be of type [inUseOtherUserError].
//
// onDone must be called when the HTTP request is done.
func (s *Server) addActiveHTTPRequest(req *http.Request, actor *actor) (onDone func(), err error) {
if actor == nil {
return nil, errors.New("internal error: nil actor")
}
func (s *Server) addActiveHTTPRequest(req *http.Request) (onDone func(), err error) {
lb := s.mustBackend()
@ -359,51 +263,12 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, actor *actor) (onDone f
s.mu.Lock()
defer s.mu.Unlock()
if err := s.checkConnIdentityLocked(actor); err != nil {
if err := s.checkConnIdentityLocked(); err != nil {
return nil, err
}
mak.Set(&s.activeReqs, req, actor)
if len(s.activeReqs) == 1 {
if envknob.GOOS() == "windows" && !actor.IsLocalSystem() {
// Tell the LocalBackend about the identity we're now running as,
// unless its the SYSTEM user. That user is not a real account and
// doesn't have a home directory.
uid, err := lb.SetCurrentUser(actor)
if err != nil {
return nil, err
}
if s.lastUserID != uid {
if s.lastUserID != "" {
doReset = true
}
s.lastUserID = uid
}
}
}
onDone = func() {
s.mu.Lock()
delete(s.activeReqs, req)
remain := len(s.activeReqs)
s.mu.Unlock()
if remain == 0 && s.resetOnZero {
if lb.InServerMode() {
s.logf("client disconnected; staying alive in server mode")
} else {
s.logf("client disconnected; stopping server")
lb.ResetForClientDisconnect()
}
}
// Wake up callers waiting for the server to be idle:
if remain == 0 {
s.mu.Lock()
s.zeroReqWaiter.wakeAll()
s.mu.Unlock()
}
}
return onDone, nil
@ -476,10 +341,7 @@ func (s *Server) Run(ctx context.Context, ln net.Listener) error {
hs := &http.Server{
Handler: http.HandlerFunc(s.serveHTTP),
BaseContext: func(_ net.Listener) context.Context { return ctx },
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
return contextWithActor(ctx, s.logf, c)
},
ErrorLog: logger.StdLogger(logger.WithPrefix(s.logf, "ipnserver: ")),
ErrorLog: logger.StdLogger(logger.WithPrefix(s.logf, "ipnserver: ")),
}
if err := hs.Serve(ln); err != nil {
if err := ctx.Err(); err != nil {

View File

@ -22,7 +22,6 @@ import (
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
@ -94,9 +93,6 @@ type Handler struct {
// (operator user).
PermitWrite bool
// Actor is the identity of the client connected to the Handler.
Actor ipnauth.Actor
b *ipnlocal.LocalBackend
logf logger.Logf
backendLogID logid.PublicID
@ -361,9 +357,6 @@ func authorizeServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeC
if !configIn.HasPathHandler() {
return nil
}
if h.Actor.IsLocalAdmin(h.b.OperatorUserID()) {
return nil
}
switch goos {
case "windows":
return errors.New("must be a Windows local admin to serve a path")
@ -452,7 +445,7 @@ func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
ctx := r.Context()
enc := json.NewEncoder(w)
h.b.WatchNotificationsAs(ctx, h.Actor, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) {
h.b.WatchNotificationsAs(ctx, nil, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) {
err := enc.Encode(roNotify)
if err != nil {
h.logf("json.Encode: %v", err)
@ -472,7 +465,7 @@ func (h *Handler) serveLoginInteractive(w http.ResponseWriter, r *http.Request)
http.Error(w, "want POST", http.StatusBadRequest)
return
}
h.b.StartLoginInteractiveAs(r.Context(), h.Actor)
h.b.StartLoginInteractiveAs(r.Context(), nil)
w.WriteHeader(http.StatusNoContent)
return
}