mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
control/controlclient: document test TestClientsReusingKeys.
The test is straightforward, but it's a little perplexing if you're not overly familiar with controlclient. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
parent
8296c934ac
commit
cbb1e2e853
@ -1167,6 +1167,12 @@ func authURLForPOST(authURL string) string {
|
|||||||
return authURL[:i] + "/login?refresh=true&next_url=" + url.PathEscape(authURL[i:])
|
return authURL[:i] + "/login?refresh=true&next_url=" + url.PathEscape(authURL[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postAuthURL manually executes the OAuth login flow, starting at
|
||||||
|
// authURL and claiming to be user. This flow will only work correctly
|
||||||
|
// if the control server is configured with the "None" auth provider,
|
||||||
|
// which blindly accepts the provided user and produces a cookie for
|
||||||
|
// them. postAuthURL returns the auth cookie produced by the control
|
||||||
|
// server.
|
||||||
func postAuthURL(t *testing.T, ctx context.Context, httpc *http.Client, user string, authURL string) *http.Cookie {
|
func postAuthURL(t *testing.T, ctx context.Context, httpc *http.Client, user string, authURL string) *http.Cookie {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
"tailscale.io/control" // not yet released
|
"tailscale.io/control" // not yet released
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test that when there are two controlclient connections using the
|
||||||
|
// same credentials, the later one disconnects the earlier one.
|
||||||
func TestClientsReusingKeys(t *testing.T) {
|
func TestClientsReusingKeys(t *testing.T) {
|
||||||
tmpdir, err := ioutil.TempDir("", "control-test-")
|
tmpdir, err := ioutil.TempDir("", "control-test-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -31,22 +33,24 @@ func TestClientsReusingKeys(t *testing.T) {
|
|||||||
httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
server.ServeHTTP(w, r)
|
server.ServeHTTP(w, r)
|
||||||
}))
|
}))
|
||||||
httpc := httpsrv.Client()
|
|
||||||
httpc.Jar, err = cookiejar.New(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
server, err = control.New(tmpdir, tmpdir, httpsrv.URL, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
server.QuietLogging = true
|
|
||||||
defer func() {
|
defer func() {
|
||||||
httpsrv.CloseClientConnections()
|
httpsrv.CloseClientConnections()
|
||||||
httpsrv.Close()
|
httpsrv.Close()
|
||||||
os.RemoveAll(tmpdir)
|
os.RemoveAll(tmpdir)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
httpc := httpsrv.Client()
|
||||||
|
httpc.Jar, err = cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err = control.New(tmpdir, tmpdir, httpsrv.URL, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
server.QuietLogging = true
|
||||||
|
|
||||||
hi := NewHostinfo()
|
hi := NewHostinfo()
|
||||||
hi.FrontendLogID = "go-test-only"
|
hi.FrontendLogID = "go-test-only"
|
||||||
hi.BackendLogID = "go-test-only"
|
hi.BackendLogID = "go-test-only"
|
||||||
@ -63,13 +67,20 @@ func TestClientsReusingKeys(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use a cancelable context so that goroutines blocking in
|
||||||
|
// PollNetMap shut down when the test exits.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// Execute c1's login flow: TryLogin to get an auth URL,
|
||||||
|
// postAuthURL to execute the (faked) OAuth segment of the flow,
|
||||||
|
// and WaitLoginURL to complete the login on the client end.
|
||||||
|
const user = "testuser1@tailscale.onmicrosoft.com"
|
||||||
authURL, err := c1.TryLogin(ctx, nil, 0)
|
authURL, err := c1.TryLogin(ctx, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
const user = "testuser1@tailscale.onmicrosoft.com"
|
|
||||||
postAuthURL(t, ctx, httpc, user, authURL)
|
postAuthURL(t, ctx, httpc, user, authURL)
|
||||||
newURL, err := c1.WaitLoginURL(ctx, authURL)
|
newURL, err := c1.WaitLoginURL(ctx, authURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -79,10 +90,13 @@ func TestClientsReusingKeys(t *testing.T) {
|
|||||||
t.Fatalf("unexpected newURL: %s", newURL)
|
t.Fatalf("unexpected newURL: %s", newURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start c1's netmap poll in parallel with the rest of the
|
||||||
|
// test. We're expecting it to block happily, invoking the no-op
|
||||||
|
// update function periodically, then exit once c2 starts its own
|
||||||
|
// poll below.
|
||||||
pollErrCh := make(chan error)
|
pollErrCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
err := c1.PollNetMap(ctx, -1, func(netMap *NetworkMap) {})
|
pollErrCh <- c1.PollNetMap(ctx, -1, func(netMap *NetworkMap) {})
|
||||||
pollErrCh <- err
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -91,6 +105,8 @@ func TestClientsReusingKeys(t *testing.T) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connect c2, reusing c1's credentials. In other words, c2 *is*
|
||||||
|
// c1 from the server's perspective.
|
||||||
c2, err := NewDirect(Options{
|
c2, err := NewDirect(Options{
|
||||||
ServerURL: httpsrv.URL,
|
ServerURL: httpsrv.URL,
|
||||||
HTTPTestClient: httpsrv.Client(),
|
HTTPTestClient: httpsrv.Client(),
|
||||||
@ -112,18 +128,24 @@ func TestClientsReusingKeys(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
// We don't expect to be given an authURL, our credentials from c1
|
||||||
|
// should still be good.
|
||||||
if authURL != "" {
|
if authURL != "" {
|
||||||
t.Errorf("unexpected authURL %s", authURL)
|
t.Errorf("unexpected authURL %s", authURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request a single netmap, so this function returns promptly
|
||||||
|
// instead of blocking like c1's PollNetMap.
|
||||||
err = c2.PollNetMap(ctx, 1, func(netMap *NetworkMap) {})
|
err = c2.PollNetMap(ctx, 1, func(netMap *NetworkMap) {})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that c2 connected and got a netmap, we expect c1's poll to
|
||||||
|
// have exited.
|
||||||
select {
|
select {
|
||||||
case err := <-pollErrCh:
|
case err := <-pollErrCh:
|
||||||
t.Logf("expected poll error: %v", err)
|
t.Logf("c1: netmap poll aborted as expected (%v)", err)
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(5 * time.Second):
|
||||||
t.Fatal("first client poll failed to close")
|
t.Fatal("first client poll failed to close")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user