ipn/ipnauth, safesocket: defer named pipe client's token retrieval until ipnserver needs it
An error returned by net.Listener.Accept() causes the owning http.Server to shut down. With the deprecation of net.Error.Temporary(), there's no way for the http.Server to test whether the returned error is temporary / retryable or not (see golang/go#66252). Because of that, errors returned by (*safesocket.winIOPipeListener).Accept() cause the LocalAPI server (aka ipnserver.Server) to shut down, and tailscaled process to exit. While this might be acceptable in the case of non-recoverable errors, such as programmer errors, we shouldn't shut down the entire tailscaled process for client- or connection-specific errors, such as when we couldn't obtain the client's access token because the client attempts to connect at the Anonymous impersonation level. Instead, the LocalAPI server should gracefully handle these errors by denying access and returning a 401 Unauthorized to the client. In tailscale/tscert#15, we fixed a known bug where Caddy and other apps using tscert would attempt to connect at the Anonymous impersonation level and fail. However, we should also fix this on the tailscaled side to prevent a potential DoS, where a local app could deliberately open the Tailscale LocalAPI named pipe at the Anonymous impersonation level and cause tailscaled to exit. In this PR, we defer token retrieval until (*WindowsClientConn).Token() is called and propagate the returned token or error via ipnauth.GetConnIdentity() to ipnserver, which handles it the same way as other ipnauth-related errors. Fixes #18212 Fixes tailscale/tscert#13 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user