ipn/ipnauth: improve the Windows token administrator check

(*Token).IsAdministrator is supposed to return true even when the user is
running with a UAC limited token. The idea is that, for the purposes of
this check, we don't care whether the user is *currently* running with
full Admin rights, we just want to know whether the user can
*potentially* do so.

We accomplish this by querying for the token's "linked token," which
should be the fully-elevated variant, and checking its group memberships.

We also switch ipn/ipnserver/(*Server).connIsLocalAdmin to use the elevation
check to preserve those semantics for tailscale serve; I want the
IsAdministrator check to be used for less sensitive things like toggling
auto-update on and off.

Fixes #10036

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
This commit is contained in:
Aaron Klotz 2023-10-31 14:37:04 -06:00
parent e5dcf7bdde
commit fbc18410ad
3 changed files with 49 additions and 9 deletions

View File

@ -13,6 +13,7 @@
"tailscale.com/ipn"
"tailscale.com/safesocket"
"tailscale.com/types/logger"
"tailscale.com/util/winutil"
)
// GetConnIdentity extracts the identity information from the connection
@ -64,7 +65,28 @@ func (t *token) IsAdministrator() (bool, error) {
return false, err
}
return t.t.IsMember(baSID)
isMember, err := t.t.IsMember(baSID)
if err != nil {
return false, err
}
if isMember {
return true, nil
}
isLimited, err := winutil.IsTokenLimited(t.t)
if err != nil || !isLimited {
return false, err
}
// Try to obtain a linked token, and if present, check it.
// (This should be the elevated token associated with limited UAC accounts.)
linkedToken, err := t.t.GetLinkedToken()
if err != nil {
return false, err
}
defer linkedToken.Close()
return linkedToken.IsMember(baSID)
}
func (t *token) IsElevated() bool {

View File

@ -367,8 +367,7 @@ func (s *Server) connCanFetchCerts(ci *ipnauth.ConnIdentity) bool {
// connIsLocalAdmin reports whether ci has administrative access to the local
// machine, for whatever that means with respect to the current OS.
//
// This returns true only on Windows machines when the client user is a
// member of the built-in Administrators group (but not necessarily elevated).
// This returns true only on Windows machines when the client user is elevated.
// This is useful because, on Windows, tailscaled itself always runs with
// elevated rights: we want to avoid privilege escalation for certain mutative operations.
func (s *Server) connIsLocalAdmin(ci *ipnauth.ConnIdentity) bool {
@ -381,12 +380,7 @@ func (s *Server) connIsLocalAdmin(ci *ipnauth.ConnIdentity) bool {
}
defer tok.Close()
isAdmin, err := tok.IsAdministrator()
if err != nil {
s.logf("ipnauth.WindowsToken.IsAdministrator() error: %v", err)
return false
}
return isAdmin
return tok.IsElevated()
}
// addActiveHTTPRequest adds c to the server's list of active HTTP requests.

View File

@ -367,6 +367,30 @@ func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup,
return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil
}
type tokenElevationType int32
const (
tokenElevationTypeDefault tokenElevationType = 1
tokenElevationTypeFull tokenElevationType = 2
tokenElevationTypeLimited tokenElevationType = 3
)
func getTokenElevationType(token windows.Token) (result tokenElevationType, err error) {
var actualLen uint32
p := (*byte)(unsafe.Pointer(&result))
err = windows.GetTokenInformation(token, windows.TokenElevationType, p, uint32(unsafe.Sizeof(result)), &actualLen)
return result, err
}
// IsTokenLimited returns whether token is a limited UAC token.
func IsTokenLimited(token windows.Token) (bool, error) {
elevationType, err := getTokenElevationType(token)
if err != nil {
return false, err
}
return elevationType == tokenElevationTypeLimited, nil
}
// UserSIDs contains the SIDs for a Windows NT token object's associated user
// as well as its primary group.
type UserSIDs struct {