This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

View File

@@ -14,8 +14,8 @@ import (
"runtime"
"strconv"
"github.com/tailscale/peercred"
"tailscale.com/envknob"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn"
"tailscale.com/safesocket"
"tailscale.com/types/logger"
@@ -63,8 +63,8 @@ type ConnIdentity struct {
notWindows bool // runtime.GOOS != "windows"
// Fields used when NotWindows:
isUnixSock bool // Conn is a *net.UnixConn
creds *peercred.Creds // or nil
isUnixSock bool // Conn is a *net.UnixConn
creds PeerCreds // or nil if peercred.Get was not implemented on this OS
// Used on Windows:
// TODO(bradfitz): merge these into the peercreds package and
@@ -78,6 +78,13 @@ type ConnIdentity struct {
// It's suitable for passing to LookupUserFromID (os/user.LookupId) on any
// operating system.
func (ci *ConnIdentity) WindowsUserID() ipn.WindowsUserID {
if !buildfeatures.HasDebug && runtime.GOOS != "windows" {
// This function is only implemented on non-Windows for simulating
// Windows in tests. But that test (per comments below) is broken
// anyway. So disable this testing path in non-debug builds
// and just do the thing that optimizes away.
return ""
}
if envknob.GOOS() != "windows" {
return ""
}
@@ -97,9 +104,18 @@ func (ci *ConnIdentity) WindowsUserID() ipn.WindowsUserID {
return ""
}
func (ci *ConnIdentity) Pid() int { return ci.pid }
func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock }
func (ci *ConnIdentity) Creds() *peercred.Creds { return ci.creds }
func (ci *ConnIdentity) Pid() int { return ci.pid }
func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock }
func (ci *ConnIdentity) Creds() PeerCreds { return ci.creds }
// PeerCreds is the interface for a github.com/tailscale/peercred.Creds,
// if linked into the binary.
//
// (It's not used on some platforms, or if ts_omit_unixsocketidentity is set.)
type PeerCreds interface {
UserID() (uid string, ok bool)
PID() (pid int, ok bool)
}
var metricIssue869Workaround = clientmetric.NewCounter("issue_869_workaround")

View File

@@ -1,14 +1,13 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows
//go:build !windows && ts_omit_unixsocketidentity
package ipnauth
import (
"net"
"github.com/tailscale/peercred"
"tailscale.com/types/logger"
)
@@ -16,12 +15,7 @@ import (
// based on the user who owns the other end of the connection.
// and couldn't. The returned connIdentity has NotWindows set to true.
func GetConnIdentity(_ logger.Logf, c net.Conn) (ci *ConnIdentity, err error) {
ci = &ConnIdentity{conn: c, notWindows: true}
_, ci.isUnixSock = c.(*net.UnixConn)
if ci.creds, _ = peercred.Get(c); ci.creds != nil {
ci.pid, _ = ci.creds.PID()
}
return ci, nil
return &ConnIdentity{conn: c, notWindows: true}, nil
}
// WindowsToken is unsupported when GOOS != windows and always returns

37
vendor/tailscale.com/ipn/ipnauth/ipnauth_unix_creds.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !windows && !ts_omit_unixsocketidentity
package ipnauth
import (
"net"
"github.com/tailscale/peercred"
"tailscale.com/types/logger"
)
// GetConnIdentity extracts the identity information from the connection
// based on the user who owns the other end of the connection.
// and couldn't. The returned connIdentity has NotWindows set to true.
func GetConnIdentity(_ logger.Logf, c net.Conn) (ci *ConnIdentity, err error) {
ci = &ConnIdentity{conn: c, notWindows: true}
_, ci.isUnixSock = c.(*net.UnixConn)
if creds, err := peercred.Get(c); err == nil {
ci.creds = creds
ci.pid, _ = ci.creds.PID()
} else if err == peercred.ErrNotImplemented {
// peercred.Get is not implemented on this OS (such as OpenBSD)
// Just leave creds as nil, as documented.
} else {
return nil, err
}
return ci, nil
}
// WindowsToken is unsupported when GOOS != windows and always returns
// ErrNotImplemented.
func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) {
return nil, ErrNotImplemented
}

View File

@@ -25,6 +25,12 @@ func GetConnIdentity(logf logger.Logf, c net.Conn) (ci *ConnIdentity, err error)
if !ok {
return nil, fmt.Errorf("not a WindowsClientConn: %T", c)
}
if err := wcc.CheckToken(); err != nil {
// Failure to obtain a token means the client cannot be authenticated.
// We don't care about the exact error, but it typically means the client
// attempted to connect at the Anonymous impersonation level.
return nil, fmt.Errorf("authentication failed: %w", err)
}
ci.pid, err = wcc.ClientPID()
if err != nil {
return nil, err
@@ -169,26 +175,13 @@ func (t *token) IsUID(uid ipn.WindowsUserID) bool {
// WindowsToken returns the WindowsToken representing the security context
// of the connection's client.
func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) {
var wcc *safesocket.WindowsClientConn
var ok bool
if wcc, ok = ci.conn.(*safesocket.WindowsClientConn); !ok {
wcc, ok := ci.conn.(*safesocket.WindowsClientConn)
if !ok {
return nil, fmt.Errorf("not a WindowsClientConn: %T", ci.conn)
}
// We duplicate the token's handle so that the WindowsToken we return may have
// a lifetime independent from the original connection.
var h windows.Handle
if err := windows.DuplicateHandle(
windows.CurrentProcess(),
windows.Handle(wcc.Token()),
windows.CurrentProcess(),
&h,
0,
false,
windows.DUPLICATE_SAME_ACCESS,
); err != nil {
token, err := wcc.Token()
if err != nil {
return nil, err
}
return newToken(windows.Token(h)), nil
return newToken(token), nil
}

View File

@@ -8,9 +8,11 @@ import (
"fmt"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/pkey"
"tailscale.com/util/syspolicy/policyclient"
)
type actorWithPolicyChecks struct{ Actor }
@@ -50,10 +52,13 @@ func (a actorWithPolicyChecks) CheckProfileAccess(profile ipn.LoginProfileView,
// TODO(nickkhyl): unexport it when we move [ipn.Actor] implementations from [ipnserver]
// and corp to this package.
func CheckDisconnectPolicy(actor Actor, profile ipn.LoginProfileView, reason string, auditFn AuditLogFunc) error {
if alwaysOn, _ := syspolicy.GetBoolean(syspolicy.AlwaysOn, false); !alwaysOn {
if !buildfeatures.HasSystemPolicy {
return nil
}
if allowWithReason, _ := syspolicy.GetBoolean(syspolicy.AlwaysOnOverrideWithReason, false); !allowWithReason {
if alwaysOn, _ := policyclient.Get().GetBoolean(pkey.AlwaysOn, false); !alwaysOn {
return nil
}
if allowWithReason, _ := policyclient.Get().GetBoolean(pkey.AlwaysOnOverrideWithReason, false); !allowWithReason {
return errors.New("disconnect not allowed: always-on mode is enabled")
}
if reason == "" {

View File

@@ -13,6 +13,11 @@ import (
// has unlimited access.
var Self Actor = unrestricted{}
// TODO is a caller identity used when the operation is performed on behalf of a user,
// rather than by tailscaled itself, but the surrounding function is not yet extended
// to accept an [Actor] parameter. It grants the same unrestricted access as [Self].
var TODO Actor = unrestricted{}
// unrestricted is an [Actor] that has unlimited access to the currently running
// tailscaled instance. It's typically used for operations performed by tailscaled
// on its own, or upon a request from the control plane, rather on behalf of a user.
@@ -49,3 +54,10 @@ func (unrestricted) IsLocalSystem() bool { return false }
// 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 (unrestricted) IsLocalAdmin(operatorUID string) bool { return false }
// IsTailscaled reports whether the given Actor represents Tailscaled itself,
// such as [Self] or a [TODO] placeholder actor.
func IsTailscaled(a Actor) bool {
_, ok := a.(unrestricted)
return ok
}