mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-27 01:05:54 -04:00
tsnet: test key extension after server restart
Updates #19326 Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
This commit is contained in:
committed by
Gesa Stupperich
parent
ec8ab870a4
commit
317201375f
@@ -3406,3 +3406,132 @@ func TestListenMultipleEphemeralPorts(t *testing.T) {
|
||||
testMultipleEphemeral(t, lt)
|
||||
})
|
||||
}
|
||||
|
||||
// TestKeyExtensionAfterRestart verifies that a tsnet client with an expired node key
|
||||
// that has launched into an interactive login after a restart recovers when the old
|
||||
// key gets extended.
|
||||
//
|
||||
// See https://github.com/tailscale/tailscale/issues/19326.
|
||||
func TestKeyExtensionAfterRestart(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
controlURL, control := startControl(t)
|
||||
control.RequireAuth = true
|
||||
|
||||
tmp := filepath.Join(t.TempDir(), "s1")
|
||||
os.MkdirAll(tmp, 0755)
|
||||
|
||||
newServer := func() *Server {
|
||||
s := &Server{
|
||||
Dir: tmp,
|
||||
ControlURL: controlURL,
|
||||
Hostname: "s1",
|
||||
Logf: tstest.WhileTestRunningLogger(t),
|
||||
}
|
||||
t.Cleanup(func() { s.Close() })
|
||||
return s
|
||||
}
|
||||
|
||||
// Start a node as tsnet instance s1.
|
||||
s1 := newServer()
|
||||
if err := s1.Start(); err != nil {
|
||||
t.Fatalf("s1.Start: %v", err)
|
||||
}
|
||||
upErrCh := make(chan error, 1)
|
||||
go func() { _, err := s1.Up(ctx); upErrCh <- err }()
|
||||
|
||||
var initialAuthURL string
|
||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||
url := s1.lb.StatusWithoutPeers().AuthURL
|
||||
if url == "" {
|
||||
return errors.New("no AuthURL yet")
|
||||
}
|
||||
initialAuthURL = url
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("waiting for initial AuthURL: %v", err)
|
||||
}
|
||||
if !control.CompleteAuth(initialAuthURL) {
|
||||
t.Fatal("failed to complete initial AuthURL")
|
||||
}
|
||||
select {
|
||||
case err := <-upErrCh:
|
||||
if err != nil {
|
||||
t.Fatalf("s1.Up: %v", err)
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatalf("timed out waiting for s1.Up to return, s1.lb.State()=%v", s1.lb.State())
|
||||
}
|
||||
|
||||
nodePub := s1.lb.StatusWithoutPeers().Self.PublicKey
|
||||
|
||||
// Expire s1's node key.
|
||||
serverNode := control.Node(nodePub)
|
||||
if serverNode == nil {
|
||||
t.Fatalf("node %v not in control", nodePub)
|
||||
}
|
||||
serverNode.KeyExpiry = time.Now().Add(-time.Minute)
|
||||
control.UpdateNode(serverNode)
|
||||
|
||||
// Wait for s1 to transition away from the Running state.
|
||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||
if got := s1.lb.State(); got == ipn.Running {
|
||||
return errors.New("still Running")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("waiting to transition away from Running: %v", err)
|
||||
}
|
||||
|
||||
if err := s1.Close(); err != nil {
|
||||
t.Fatalf("s1.Close: %v", err)
|
||||
}
|
||||
|
||||
// Restart the node as tsnet instance s2.
|
||||
s2 := newServer()
|
||||
if err := s2.Start(); err != nil {
|
||||
t.Fatalf("s2.Start: %v", err)
|
||||
}
|
||||
s2UpErrCh := make(chan error, 1)
|
||||
go func() { _, err := s2.Up(ctx); s2UpErrCh <- err }()
|
||||
|
||||
// Wait for s2 to transition into the NeedsLogin state.
|
||||
var secondAuthURL string
|
||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||
u := s2.lb.StatusWithoutPeers().AuthURL
|
||||
if u == "" {
|
||||
return errors.New("no AuthURL yet")
|
||||
}
|
||||
secondAuthURL = u
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("waiting for s2 AuthURL: %v", err)
|
||||
}
|
||||
// We deliberately do not complete the auth.
|
||||
_ = secondAuthURL
|
||||
|
||||
// Extend the old node key.
|
||||
serverNode.KeyExpiry = time.Now().Add(24 * time.Hour)
|
||||
control.UpdateNode(serverNode)
|
||||
|
||||
// Wait for s2 to receive the netmap with the key extension info
|
||||
// and transition to Running.
|
||||
if err := tstest.WaitFor(20*time.Second, func() error {
|
||||
if got := s2.lb.State(); got != ipn.Running {
|
||||
return fmt.Errorf("in state %v; want Running", got)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("waiting to return to Running after key extension: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-s2UpErrCh:
|
||||
if err != nil {
|
||||
t.Fatalf("s2.Up: %v", err)
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
t.Fatalf("timed out waiting for s2.Up to return, s2.lb.State()=%v", s2.lb.State())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user