state: replace zcache with bounded LRU for auth cache

Replace zcache with golang-lru/v2/expirable for both the state auth
cache and the OIDC state cache. Add tuning.register_cache_max_entries
(default 1024) to cap the number of pending registration entries.

Introduce types.RegistrationData to replace caching a full *Node;
only the fields the registration callback path reads are retained.
Remove the dead HSDatabase.regCache field. Drop zgo.at/zcache/v2
from go.mod.
This commit is contained in:
Kristoffer Dalby 2026-04-09 17:27:42 +00:00
parent 3587225a88
commit 0d4f2293ff
21 changed files with 343 additions and 258 deletions

View file

@ -681,7 +681,7 @@ func TestAuthenticationFlows(t *testing.T) {
return "", err
}
nodeToRegister := types.NewRegisterAuthRequest(types.Node{
nodeToRegister := types.NewRegisterAuthRequest(&types.RegistrationData{
Hostname: "followup-success-node",
})
app.state.SetAuthCacheEntry(regID, nodeToRegister)
@ -723,7 +723,7 @@ func TestAuthenticationFlows(t *testing.T) {
return "", err
}
nodeToRegister := types.NewRegisterAuthRequest(types.Node{
nodeToRegister := types.NewRegisterAuthRequest(&types.RegistrationData{
Hostname: "followup-timeout-node",
})
app.state.SetAuthCacheEntry(regID, nodeToRegister)
@ -1341,7 +1341,7 @@ func TestAuthenticationFlows(t *testing.T) {
return "", err
}
nodeToRegister := types.NewRegisterAuthRequest(types.Node{
nodeToRegister := types.NewRegisterAuthRequest(&types.RegistrationData{
Hostname: "nil-response-node",
})
app.state.SetAuthCacheEntry(regID, nodeToRegister)
@ -2618,7 +2618,7 @@ func runInteractiveWorkflowTest(t *testing.T, tt struct {
cacheEntry, found := app.state.GetAuthCacheEntry(registrationID)
require.True(t, found, "registration cache entry should exist")
require.NotNil(t, cacheEntry, "cache entry should not be nil")
require.Equal(t, req.NodeKey, cacheEntry.Node().NodeKey(), "cache entry should have correct node key")
require.Equal(t, req.NodeKey, cacheEntry.RegistrationData().NodeKey, "cache entry should have correct node key")
}
case stepTypeAuthCompletion:
@ -3570,7 +3570,7 @@ func TestWebAuthRejectsUnauthorizedRequestTags(t *testing.T) {
// Simulate a registration cache entry (as would be created during web auth)
registrationID := types.MustAuthID()
regEntry := types.NewRegisterAuthRequest(types.Node{
regEntry := types.NewRegisterAuthRequest(&types.RegistrationData{
MachineKey: machineKey.Public(),
NodeKey: nodeKey.Public(),
Hostname: "webauth-tags-node",
@ -3633,7 +3633,7 @@ func TestWebAuthReauthWithEmptyTagsRemovesAllTags(t *testing.T) {
// Step 1: Initial registration with tags
registrationID1 := types.MustAuthID()
regEntry1 := types.NewRegisterAuthRequest(types.Node{
regEntry1 := types.NewRegisterAuthRequest(&types.RegistrationData{
MachineKey: machineKey.Public(),
NodeKey: nodeKey1.Public(),
Hostname: "reauth-untag-node",
@ -3660,7 +3660,7 @@ func TestWebAuthReauthWithEmptyTagsRemovesAllTags(t *testing.T) {
// Step 2: Reauth with EMPTY tags to untag
nodeKey2 := key.NewNode() // New node key for reauth
registrationID2 := types.MustAuthID()
regEntry2 := types.NewRegisterAuthRequest(types.Node{
regEntry2 := types.NewRegisterAuthRequest(&types.RegistrationData{
MachineKey: machineKey.Public(), // Same machine key
NodeKey: nodeKey2.Public(), // Different node key (rotation)
Hostname: "reauth-untag-node",
@ -3746,7 +3746,7 @@ func TestAuthKeyTaggedToUserOwnedViaReauth(t *testing.T) {
// Step 2: Reauth via web auth with EMPTY tags to transition to user-owned
nodeKey2 := key.NewNode() // New node key for reauth
registrationID := types.MustAuthID()
regEntry := types.NewRegisterAuthRequest(types.Node{
regEntry := types.NewRegisterAuthRequest(&types.RegistrationData{
MachineKey: machineKey.Public(), // Same machine key
NodeKey: nodeKey2.Public(), // Different node key (rotation)
Hostname: "authkey-tagged-node",
@ -3945,7 +3945,7 @@ func TestTaggedNodeWithoutUserToDifferentUser(t *testing.T) {
// This is what happens when running: headscale auth register --auth-id <id> --user alice
nodeKey2 := key.NewNode()
registrationID := types.MustAuthID()
regEntry := types.NewRegisterAuthRequest(types.Node{
regEntry := types.NewRegisterAuthRequest(&types.RegistrationData{
MachineKey: machineKey.Public(), // Same machine key as the tagged node
NodeKey: nodeKey2.Public(),
Hostname: "tagged-orphan-node",