diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 78913fc62..c98eae885 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -200,7 +200,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal tailscale.com/hostinfo from tailscale.com/control/controlclient+ tailscale.com/ipn from tailscale.com/ipn/ipnlocal+ - tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnserver + tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnserver+ tailscale.com/ipn/ipnlocal from tailscale.com/ssh/tailssh+ tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+ diff --git a/ipn/ipnauth/ipnauth.go b/ipn/ipnauth/ipnauth.go index b302f3e18..2ca6caaa3 100644 --- a/ipn/ipnauth/ipnauth.go +++ b/ipn/ipnauth/ipnauth.go @@ -44,7 +44,16 @@ type ConnIdentity struct { user *user.User } -func (ci *ConnIdentity) UserID() string { return ci.userID } +// UserID returns the local machine's userid of the connection. +// +// It's suitable for passing to LookupUserFromID (os/user.LookupId) on any +// operating system. +// +// TODO(bradfitz): it currently returns an empty string on everything +// but Windows. We should make it return the actual uid also on all supported +// peercred platforms from the creds if non-nil. +func (ci *ConnIdentity) UserID() string { return ci.userID } + func (ci *ConnIdentity) User() *user.User { return ci.user } func (ci *ConnIdentity) Pid() int { return ci.pid } func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 0ea2e1bbe..23d4d87ed 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -38,6 +38,7 @@ import ( "tailscale.com/health/healthmsg" "tailscale.com/hostinfo" "tailscale.com/ipn" + "tailscale.com/ipn/ipnauth" "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/policy" "tailscale.com/net/dns" @@ -2029,6 +2030,34 @@ func (b *LocalBackend) InServerMode() bool { return b.inServerMode } +// CheckIPNConnectionAllowed returns an error if the identity in ci should not +// be allowed to connect or make requests to the LocalAPI currently. +// +// Currently (as of 2022-11-23), this is only used on Windows to check if +// we started in server mode and ci is from an identity other than the one +// that started the server. +func (b *LocalBackend) CheckIPNConnectionAllowed(ci *ipnauth.ConnIdentity) error { + b.mu.Lock() + defer b.mu.Unlock() + serverModeUid := b.pm.CurrentUser() + if serverModeUid == "" { + // Either this platform isn't a "multi-user" platform or we're not yet + // running as one. + return nil + } + if !b.inServerMode { + return nil + } + uid := ci.UserID() + if uid == "" { + return errors.New("empty user uid in connection identity") + } + if uid != serverModeUid { + return fmt.Errorf("Tailscale running in server mode (uid=%v); connection from %q not allowed", serverModeUid, uid) + } + return nil +} + // Login implements Backend. // As of 2022-11-15, this is only exists for Android. func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) { @@ -4383,11 +4412,3 @@ func (b *LocalBackend) ListProfiles() []ipn.LoginProfile { defer b.mu.Unlock() return b.pm.Profiles() } - -// CurrentUser returns the current server mode user ID. It is only non-empty on -// Windows where we have a multi-user system. -func (b *LocalBackend) CurrentUser() string { - b.mu.Lock() - defer b.mu.Unlock() - return b.pm.CurrentUser() -} diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 03bfcaadd..b8473cfce 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -304,8 +304,8 @@ func (s *Server) checkConnIdentityLocked(ci *ipnauth.ConnIdentity) error { return inUseOtherUserError{fmt.Errorf("Tailscale already in use by %s, pid %d", active.User().Username, active.Pid())} } } - if cu := s.b.CurrentUser(); cu != "" && cu != ci.UserID() { - return inUseOtherUserError{fmt.Errorf("Tailscale already in use by %s", s.b.CurrentUser())} + if err := s.b.CheckIPNConnectionAllowed(ci); err != nil { + return inUseOtherUserError{err} } return nil }