client/web: skip check mode for non-tailscale.com control servers (#10413)

client/web: skip check mode for non-tailscale.com control servers

Only enforce check mode if the control server URL ends in
".tailscale.com".  This allows the web client to be used with headscale
(or other) control servers while we work with the project to add check
mode support (tracked in juanfont/headscale#1623).

Updates #10261

Co-authored-by: Sonia Appasamy <sonia@tailscale.com>
Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
Will Norris
2023-11-29 08:44:48 -08:00
committed by GitHub
parent ab0e25beaa
commit 26db9775f8
3 changed files with 87 additions and 27 deletions
+51 -19
View File
@@ -20,6 +20,7 @@ import (
"github.com/google/go-cmp/cmp"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/memnet"
"tailscale.com/tailcfg"
@@ -167,7 +168,7 @@ func TestGetTailscaleBrowserSession(t *testing.T) {
lal := memnet.Listen("local-tailscaled.sock:80")
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()
go localapi.Serve(lal)
@@ -334,6 +335,7 @@ func TestAuthorizeRequest(t *testing.T) {
localapi := mockLocalAPI(t,
map[string]*apitype.WhoIsResponse{remoteIP: remoteNode},
func() *ipnstate.PeerStatus { return self },
nil,
)
defer localapi.Close()
go localapi.Serve(lal)
@@ -433,11 +435,16 @@ func TestServeAuth(t *testing.T) {
ProfilePicURL: user.ProfilePicURL,
}
testControlURL := &defaultControlURL
lal := memnet.Listen("local-tailscaled.sock:80")
defer lal.Close()
localapi := mockLocalAPI(t,
map[string]*apitype.WhoIsResponse{remoteIP: remoteNode},
func() *ipnstate.PeerStatus { return self },
func() *ipn.Prefs {
return &ipn.Prefs{ControlURL: *testControlURL}
},
)
defer localapi.Close()
go localapi.Serve(lal)
@@ -461,7 +468,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathSuccess,
AuthURL: testControlURL + testAuthPathSuccess,
AuthURL: *testControlURL + testAuthPathSuccess,
})
failureCookie := "ts-cookie-failure"
s.browserSessions.Store(failureCookie, &browserSession{
@@ -470,7 +477,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathError,
AuthURL: testControlURL + testAuthPathError,
AuthURL: *testControlURL + testAuthPathError,
})
expiredCookie := "ts-cookie-expired"
s.browserSessions.Store(expiredCookie, &browserSession{
@@ -479,12 +486,13 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: sixtyDaysAgo,
AuthID: "/a/old-auth-url",
AuthURL: testControlURL + "/a/old-auth-url",
AuthURL: *testControlURL + "/a/old-auth-url",
})
tests := []struct {
name string
controlURL string // if empty, defaultControlURL is used
cookie string // cookie attached to request
wantNewCookie bool // want new cookie generated during request
wantSession *browserSession // session associated w/ cookie after request
@@ -505,7 +513,7 @@ func TestServeAuth(t *testing.T) {
name: "new-session",
path: "/api/auth/session/new",
wantStatus: http.StatusOK,
wantResp: &newSessionAuthResponse{AuthURL: testControlURL + testAuthPath},
wantResp: &newSessionAuthResponse{AuthURL: *testControlURL + testAuthPath},
wantNewCookie: true,
wantSession: &browserSession{
ID: "GENERATED_ID", // gets swapped for newly created ID by test
@@ -513,7 +521,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: timeNow,
AuthID: testAuthPath,
AuthURL: testControlURL + testAuthPath,
AuthURL: *testControlURL + testAuthPath,
Authenticated: false,
},
},
@@ -529,7 +537,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathSuccess,
AuthURL: testControlURL + testAuthPathSuccess,
AuthURL: *testControlURL + testAuthPathSuccess,
Authenticated: false,
},
},
@@ -538,14 +546,14 @@ func TestServeAuth(t *testing.T) {
path: "/api/auth/session/new", // should not create new session
cookie: successCookie,
wantStatus: http.StatusOK,
wantResp: &newSessionAuthResponse{AuthURL: testControlURL + testAuthPathSuccess},
wantResp: &newSessionAuthResponse{AuthURL: *testControlURL + testAuthPathSuccess},
wantSession: &browserSession{
ID: successCookie,
SrcNode: remoteNode.Node.ID,
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathSuccess,
AuthURL: testControlURL + testAuthPathSuccess,
AuthURL: *testControlURL + testAuthPathSuccess,
Authenticated: false,
},
},
@@ -561,7 +569,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathSuccess,
AuthURL: testControlURL + testAuthPathSuccess,
AuthURL: *testControlURL + testAuthPathSuccess,
Authenticated: true,
},
},
@@ -577,7 +585,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: oneHourAgo,
AuthID: testAuthPathSuccess,
AuthURL: testControlURL + testAuthPathSuccess,
AuthURL: *testControlURL + testAuthPathSuccess,
Authenticated: true,
},
},
@@ -594,7 +602,7 @@ func TestServeAuth(t *testing.T) {
path: "/api/auth/session/new",
cookie: failureCookie,
wantStatus: http.StatusOK,
wantResp: &newSessionAuthResponse{AuthURL: testControlURL + testAuthPath},
wantResp: &newSessionAuthResponse{AuthURL: *testControlURL + testAuthPath},
wantNewCookie: true,
wantSession: &browserSession{
ID: "GENERATED_ID",
@@ -602,7 +610,7 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: timeNow,
AuthID: testAuthPath,
AuthURL: testControlURL + testAuthPath,
AuthURL: *testControlURL + testAuthPath,
Authenticated: false,
},
},
@@ -611,7 +619,7 @@ func TestServeAuth(t *testing.T) {
path: "/api/auth/session/new",
cookie: expiredCookie,
wantStatus: http.StatusOK,
wantResp: &newSessionAuthResponse{AuthURL: testControlURL + testAuthPath},
wantResp: &newSessionAuthResponse{AuthURL: *testControlURL + testAuthPath},
wantNewCookie: true,
wantSession: &browserSession{
ID: "GENERATED_ID",
@@ -619,13 +627,34 @@ func TestServeAuth(t *testing.T) {
SrcUser: user.ID,
Created: timeNow,
AuthID: testAuthPath,
AuthURL: testControlURL + testAuthPath,
AuthURL: *testControlURL + testAuthPath,
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 {
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.RemoteAddr = remoteIP
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")
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()
go localapi.Serve(lal)
@@ -771,7 +800,7 @@ func TestRequireTailscaleIP(t *testing.T) {
}
var (
testControlURL = "http://localhost:8080"
defaultControlURL = "https://controlplane.tailscale.com"
testAuthPath = "/a/12345"
testAuthPathSuccess = "/a/will-succeed"
testAuthPathError = "/a/will-error"
@@ -783,7 +812,7 @@ var (
// self accepts a function that resolves to a self node status,
// so that tests may swap out the /localapi/v0/status response
// 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) {
switch r.URL.Path {
case "/localapi/v0/whois":
@@ -800,6 +829,9 @@ func mockLocalAPI(t *testing.T, whoIs map[string]*apitype.WhoIsResponse, self fu
case "/localapi/v0/status":
writeJSON(w, ipnstate.Status{Self: self()})
return
case "/localapi/v0/prefs":
writeJSON(w, prefs())
return
default:
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) {
// Create new dummy auth URL.
return &tailcfg.WebClientAuthResponse{ID: testAuthPath, URL: testControlURL + testAuthPath}, nil
return &tailcfg.WebClientAuthResponse{ID: testAuthPath, URL: defaultControlURL + testAuthPath}, nil
}
func mockWaitAuthURL(_ context.Context, id string, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {