tstest/integration/testcontrol: expire individual node keys

This adds testcontrol support for expiring individual node keys,
in order to enable test scenarios involving to key-expiry and
 extension.

Updates #19326

Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
This commit is contained in:
Gesa Stupperich
2026-06-11 19:21:09 +01:00
committed by Gesa Stupperich
parent 5be05f2c0d
commit ec8ab870a4
+24 -11
View File
@@ -910,7 +910,8 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
}
// If this is a followup request, wait until interactive followup URL visit complete.
if req.Followup != "" {
isFollowup := req.Followup != ""
if isFollowup {
followupURL, err := url.Parse(req.Followup)
if err != nil {
panic(err)
@@ -926,18 +927,22 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
// some follow-ups? For now all are successes.
}
// The in-memory list of nodes, users, and logins is keyed by
// the node key. If the node key changes, update all the data stores
// to use the new node key.
// On a key rotation (OldNodeKey set and known to s.nodes), stage
// the new key as a candidate entry but keep the old key's entry
// alive so an in-flight map poll can still receive updates while
// the user completes the auth URL.
s.mu.Lock()
if _, oldNodeKeyOk := s.nodes[req.OldNodeKey]; oldNodeKeyOk {
if _, newNodeKeyOk := s.nodes[req.NodeKey]; !newNodeKeyOk {
s.nodes[req.OldNodeKey].Key = req.NodeKey
s.nodes[req.NodeKey] = s.nodes[req.OldNodeKey]
cloned := s.nodes[req.OldNodeKey].Clone()
cloned.Key = req.NodeKey
s.nodes[req.NodeKey] = cloned
s.users[req.NodeKey] = s.users[req.OldNodeKey]
s.logins[req.NodeKey] = s.logins[req.OldNodeKey]
}
if isFollowup {
// The user has completed the auth URL, the new key
// is now authoritative. Retire the old key's entry.
delete(s.nodes, req.OldNodeKey)
delete(s.users, req.OldNodeKey)
delete(s.logins, req.OldNodeKey)
@@ -997,11 +1002,19 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
}
s.nodes[nk] = node
}
// Consider a node key expired if allExpired is set or if the nodeKey has
// an expiry time in the past. This allows tests to set per-node KeyExpiry
// via UpdateNode to simulate an admin-triggered or time-based expiry.
nodeKeyExpired := s.allExpired
if !nodeKeyExpired && req.OldNodeKey.IsZero() {
if n, ok := s.nodes[nk]; ok && !n.KeyExpiry.IsZero() && n.KeyExpiry.Before(time.Now()) {
nodeKeyExpired = true
}
}
requireAuth := s.RequireAuth
if requireAuth && s.nodeKeyAuthed.Contains(nk) {
if requireAuth && s.nodeKeyAuthed.Contains(nk) && !nodeKeyExpired {
requireAuth = false
}
allExpired := s.allExpired
s.mu.Unlock()
authURL := ""
@@ -1014,7 +1027,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
res, err := s.encode(false, tailcfg.RegisterResponse{
User: *user,
Login: *login,
NodeKeyExpired: allExpired,
NodeKeyExpired: nodeKeyExpired,
MachineAuthorized: machineAuthorized,
AuthURL: authURL,
})