@ -20,6 +20,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/memnet"
"tailscale.com/net/memnet"
"tailscale.com/tailcfg"
"tailscale.com/tailcfg"
@ -167,7 +168,7 @@ func TestGetTailscaleBrowserSession(t *testing.T) {
lal := memnet . Listen ( "local-tailscaled.sock:80" )
lal := memnet . Listen ( "local-tailscaled.sock:80" )
defer lal . Close ( )
defer lal . Close ( )
localapi := mockLocalAPI ( t , tailnetNodes , func ( ) * ipnstate . PeerStatus { return selfNode } )
localapi := mockLocalAPI ( t , tailnetNodes , func ( ) * ipnstate . PeerStatus { return selfNode } , nil )
defer localapi . Close ( )
defer localapi . Close ( )
go localapi . Serve ( lal )
go localapi . Serve ( lal )
@ -334,6 +335,7 @@ func TestAuthorizeRequest(t *testing.T) {
localapi := mockLocalAPI ( t ,
localapi := mockLocalAPI ( t ,
map [ string ] * apitype . WhoIsResponse { remoteIP : remoteNode } ,
map [ string ] * apitype . WhoIsResponse { remoteIP : remoteNode } ,
func ( ) * ipnstate . PeerStatus { return self } ,
func ( ) * ipnstate . PeerStatus { return self } ,
nil ,
)
)
defer localapi . Close ( )
defer localapi . Close ( )
go localapi . Serve ( lal )
go localapi . Serve ( lal )
@ -433,11 +435,16 @@ func TestServeAuth(t *testing.T) {
ProfilePicURL : user . ProfilePicURL ,
ProfilePicURL : user . ProfilePicURL ,
}
}
testControlURL := & defaultControlURL
lal := memnet . Listen ( "local-tailscaled.sock:80" )
lal := memnet . Listen ( "local-tailscaled.sock:80" )
defer lal . Close ( )
defer lal . Close ( )
localapi := mockLocalAPI ( t ,
localapi := mockLocalAPI ( t ,
map [ string ] * apitype . WhoIsResponse { remoteIP : remoteNode } ,
map [ string ] * apitype . WhoIsResponse { remoteIP : remoteNode } ,
func ( ) * ipnstate . PeerStatus { return self } ,
func ( ) * ipnstate . PeerStatus { return self } ,
func ( ) * ipn . Prefs {
return & ipn . Prefs { ControlURL : * testControlURL }
} ,
)
)
defer localapi . Close ( )
defer localapi . Close ( )
go localapi . Serve ( lal )
go localapi . Serve ( lal )
@ -461,7 +468,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathSuccess ,
AuthID : testAuthPathSuccess ,
AuthURL : testControlURL + testAuthPathSuccess ,
AuthURL : * testControlURL + testAuthPathSuccess ,
} )
} )
failureCookie := "ts-cookie-failure"
failureCookie := "ts-cookie-failure"
s . browserSessions . Store ( failureCookie , & browserSession {
s . browserSessions . Store ( failureCookie , & browserSession {
@ -470,7 +477,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathError ,
AuthID : testAuthPathError ,
AuthURL : testControlURL + testAuthPathError ,
AuthURL : * testControlURL + testAuthPathError ,
} )
} )
expiredCookie := "ts-cookie-expired"
expiredCookie := "ts-cookie-expired"
s . browserSessions . Store ( expiredCookie , & browserSession {
s . browserSessions . Store ( expiredCookie , & browserSession {
@ -479,12 +486,13 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : sixtyDaysAgo ,
Created : sixtyDaysAgo ,
AuthID : "/a/old-auth-url" ,
AuthID : "/a/old-auth-url" ,
AuthURL : testControlURL + "/a/old-auth-url" ,
AuthURL : * testControlURL + "/a/old-auth-url" ,
} )
} )
tests := [ ] struct {
tests := [ ] struct {
name string
name string
controlURL string // if empty, defaultControlURL is used
cookie string // cookie attached to request
cookie string // cookie attached to request
wantNewCookie bool // want new cookie generated during request
wantNewCookie bool // want new cookie generated during request
wantSession * browserSession // session associated w/ cookie after request
wantSession * browserSession // session associated w/ cookie after request
@ -505,7 +513,7 @@ func TestServeAuth(t *testing.T) {
name : "new-session" ,
name : "new-session" ,
path : "/api/auth/session/new" ,
path : "/api/auth/session/new" ,
wantStatus : http . StatusOK ,
wantStatus : http . StatusOK ,
wantResp : & newSessionAuthResponse { AuthURL : testControlURL + testAuthPath } ,
wantResp : & newSessionAuthResponse { AuthURL : * testControlURL + testAuthPath } ,
wantNewCookie : true ,
wantNewCookie : true ,
wantSession : & browserSession {
wantSession : & browserSession {
ID : "GENERATED_ID" , // gets swapped for newly created ID by test
ID : "GENERATED_ID" , // gets swapped for newly created ID by test
@ -513,7 +521,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : timeNow ,
Created : timeNow ,
AuthID : testAuthPath ,
AuthID : testAuthPath ,
AuthURL : testControlURL + testAuthPath ,
AuthURL : * testControlURL + testAuthPath ,
Authenticated : false ,
Authenticated : false ,
} ,
} ,
} ,
} ,
@ -529,7 +537,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathSuccess ,
AuthID : testAuthPathSuccess ,
AuthURL : testControlURL + testAuthPathSuccess ,
AuthURL : * testControlURL + testAuthPathSuccess ,
Authenticated : false ,
Authenticated : false ,
} ,
} ,
} ,
} ,
@ -538,14 +546,14 @@ func TestServeAuth(t *testing.T) {
path : "/api/auth/session/new" , // should not create new session
path : "/api/auth/session/new" , // should not create new session
cookie : successCookie ,
cookie : successCookie ,
wantStatus : http . StatusOK ,
wantStatus : http . StatusOK ,
wantResp : & newSessionAuthResponse { AuthURL : testControlURL + testAuthPathSuccess } ,
wantResp : & newSessionAuthResponse { AuthURL : * testControlURL + testAuthPathSuccess } ,
wantSession : & browserSession {
wantSession : & browserSession {
ID : successCookie ,
ID : successCookie ,
SrcNode : remoteNode . Node . ID ,
SrcNode : remoteNode . Node . ID ,
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathSuccess ,
AuthID : testAuthPathSuccess ,
AuthURL : testControlURL + testAuthPathSuccess ,
AuthURL : * testControlURL + testAuthPathSuccess ,
Authenticated : false ,
Authenticated : false ,
} ,
} ,
} ,
} ,
@ -561,7 +569,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathSuccess ,
AuthID : testAuthPathSuccess ,
AuthURL : testControlURL + testAuthPathSuccess ,
AuthURL : * testControlURL + testAuthPathSuccess ,
Authenticated : true ,
Authenticated : true ,
} ,
} ,
} ,
} ,
@ -577,7 +585,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : oneHourAgo ,
Created : oneHourAgo ,
AuthID : testAuthPathSuccess ,
AuthID : testAuthPathSuccess ,
AuthURL : testControlURL + testAuthPathSuccess ,
AuthURL : * testControlURL + testAuthPathSuccess ,
Authenticated : true ,
Authenticated : true ,
} ,
} ,
} ,
} ,
@ -594,7 +602,7 @@ func TestServeAuth(t *testing.T) {
path : "/api/auth/session/new" ,
path : "/api/auth/session/new" ,
cookie : failureCookie ,
cookie : failureCookie ,
wantStatus : http . StatusOK ,
wantStatus : http . StatusOK ,
wantResp : & newSessionAuthResponse { AuthURL : testControlURL + testAuthPath } ,
wantResp : & newSessionAuthResponse { AuthURL : * testControlURL + testAuthPath } ,
wantNewCookie : true ,
wantNewCookie : true ,
wantSession : & browserSession {
wantSession : & browserSession {
ID : "GENERATED_ID" ,
ID : "GENERATED_ID" ,
@ -602,7 +610,7 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : timeNow ,
Created : timeNow ,
AuthID : testAuthPath ,
AuthID : testAuthPath ,
AuthURL : testControlURL + testAuthPath ,
AuthURL : * testControlURL + testAuthPath ,
Authenticated : false ,
Authenticated : false ,
} ,
} ,
} ,
} ,
@ -611,7 +619,7 @@ func TestServeAuth(t *testing.T) {
path : "/api/auth/session/new" ,
path : "/api/auth/session/new" ,
cookie : expiredCookie ,
cookie : expiredCookie ,
wantStatus : http . StatusOK ,
wantStatus : http . StatusOK ,
wantResp : & newSessionAuthResponse { AuthURL : testControlURL + testAuthPath } ,
wantResp : & newSessionAuthResponse { AuthURL : * testControlURL + testAuthPath } ,
wantNewCookie : true ,
wantNewCookie : true ,
wantSession : & browserSession {
wantSession : & browserSession {
ID : "GENERATED_ID" ,
ID : "GENERATED_ID" ,
@ -619,13 +627,34 @@ func TestServeAuth(t *testing.T) {
SrcUser : user . ID ,
SrcUser : user . ID ,
Created : timeNow ,
Created : timeNow ,
AuthID : testAuthPath ,
AuthID : testAuthPath ,
AuthURL : testControlURL + testAuthPath ,
AuthURL : * testControlURL + testAuthPath ,
Authenticated : false ,
Authenticated : false ,
} ,
} ,
} ,
} ,
{
name : "control-server-no-check-mode" ,
controlURL : "http://alternate-server.com/" ,
path : "/api/auth/session/new" ,
wantStatus : http . StatusOK ,
wantResp : & newSessionAuthResponse { } ,
wantNewCookie : true ,
wantSession : & browserSession {
ID : "GENERATED_ID" , // gets swapped for newly created ID by test
SrcNode : remoteNode . Node . ID ,
SrcUser : user . ID ,
Created : timeNow ,
Authenticated : true ,
} ,
} ,
}
}
for _ , tt := range tests {
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
t . Run ( tt . name , func ( t * testing . T ) {
if tt . controlURL != "" {
testControlURL = & tt . controlURL
} else {
testControlURL = & defaultControlURL
}
r := httptest . NewRequest ( "GET" , "http://100.1.2.3:5252" + tt . path , nil )
r := httptest . NewRequest ( "GET" , "http://100.1.2.3:5252" + tt . path , nil )
r . RemoteAddr = remoteIP
r . RemoteAddr = remoteIP
r . AddCookie ( & http . Cookie { Name : sessionCookieName , Value : tt . cookie } )
r . AddCookie ( & http . Cookie { Name : sessionCookieName , Value : tt . cookie } )
@ -694,7 +723,7 @@ func TestRequireTailscaleIP(t *testing.T) {
lal := memnet . Listen ( "local-tailscaled.sock:80" )
lal := memnet . Listen ( "local-tailscaled.sock:80" )
defer lal . Close ( )
defer lal . Close ( )
localapi := mockLocalAPI ( t , nil , func ( ) * ipnstate . PeerStatus { return self } )
localapi := mockLocalAPI ( t , nil , func ( ) * ipnstate . PeerStatus { return self } , nil )
defer localapi . Close ( )
defer localapi . Close ( )
go localapi . Serve ( lal )
go localapi . Serve ( lal )
@ -771,7 +800,7 @@ func TestRequireTailscaleIP(t *testing.T) {
}
}
var (
var (
testControlURL = "http://localhost:8080 "
defaultControlURL = "https://controlplane.tailscale.com "
testAuthPath = "/a/12345"
testAuthPath = "/a/12345"
testAuthPathSuccess = "/a/will-succeed"
testAuthPathSuccess = "/a/will-succeed"
testAuthPathError = "/a/will-error"
testAuthPathError = "/a/will-error"
@ -783,7 +812,7 @@ var (
// self accepts a function that resolves to a self node status,
// self accepts a function that resolves to a self node status,
// so that tests may swap out the /localapi/v0/status response
// so that tests may swap out the /localapi/v0/status response
// as desired.
// as desired.
func mockLocalAPI ( t * testing . T , whoIs map [ string ] * apitype . WhoIsResponse , self func ( ) * ipnstate . PeerStatus ) * http . Server {
func mockLocalAPI ( t * testing . T , whoIs map [ string ] * apitype . WhoIsResponse , self func ( ) * ipnstate . PeerStatus , prefs func ( ) * ipn . Prefs ) * http . Server {
return & http . Server { Handler : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
return & http . Server { Handler : http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
switch r . URL . Path {
switch r . URL . Path {
case "/localapi/v0/whois" :
case "/localapi/v0/whois" :
@ -800,6 +829,9 @@ func mockLocalAPI(t *testing.T, whoIs map[string]*apitype.WhoIsResponse, self fu
case "/localapi/v0/status" :
case "/localapi/v0/status" :
writeJSON ( w , ipnstate . Status { Self : self ( ) } )
writeJSON ( w , ipnstate . Status { Self : self ( ) } )
return
return
case "/localapi/v0/prefs" :
writeJSON ( w , prefs ( ) )
return
default :
default :
t . Fatalf ( "unhandled localapi test endpoint %q, add to localapi handler func in test" , r . URL . Path )
t . Fatalf ( "unhandled localapi test endpoint %q, add to localapi handler func in test" , r . URL . Path )
}
}
@ -808,7 +840,7 @@ func mockLocalAPI(t *testing.T, whoIs map[string]*apitype.WhoIsResponse, self fu
func mockNewAuthURL ( _ context . Context , src tailcfg . NodeID ) ( * tailcfg . WebClientAuthResponse , error ) {
func mockNewAuthURL ( _ context . Context , src tailcfg . NodeID ) ( * tailcfg . WebClientAuthResponse , error ) {
// Create new dummy auth URL.
// Create new dummy auth URL.
return & tailcfg . WebClientAuthResponse { ID : testAuthPath , URL : tes tControlURL + testAuthPath } , nil
return & tailcfg . WebClientAuthResponse { ID : testAuthPath , URL : defaul tControlURL + testAuthPath } , nil
}
}
func mockWaitAuthURL ( _ context . Context , id string , src tailcfg . NodeID ) ( * tailcfg . WebClientAuthResponse , error ) {
func mockWaitAuthURL ( _ context . Context , id string , src tailcfg . NodeID ) ( * tailcfg . WebClientAuthResponse , error ) {