diff --git a/integration/acl_test.go b/integration/acl_test.go index 54273566..b8be400b 100644 --- a/integration/acl_test.go +++ b/integration/acl_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" @@ -324,7 +324,7 @@ func TestACLHostsInNetMapTable(t *testing.T) { user := status.User[status.Self.UserID].LoginName - assert.Len(c, status.Peer, (testCase.want[user])) + assert.Len(c, status.Peer, testCase.want[user]) }, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected peer visibility") } }) @@ -339,7 +339,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { func TestACLAllowUser80Dst(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ ACLs: []policyv2.ACL{ { @@ -396,7 +397,8 @@ func TestACLAllowUser80Dst(t *testing.T) { func TestACLDenyAllPort80(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ Groups: policyv2.Groups{ policyv2.Group("group:integration-acl-test"): []policyv2.Username{policyv2.Username("user1@"), policyv2.Username("user2@")}, @@ -446,7 +448,8 @@ func TestACLDenyAllPort80(t *testing.T) { func TestACLAllowUserDst(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ ACLs: []policyv2.ACL{ { @@ -505,7 +508,8 @@ func TestACLAllowUserDst(t *testing.T) { func TestACLAllowStarDst(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ ACLs: []policyv2.ACL{ { @@ -565,7 +569,8 @@ func TestACLAllowStarDst(t *testing.T) { func TestACLNamedHostsCanReachBySubnet(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ Hosts: policyv2.Hosts{ "all": policyv2.Prefix(netip.MustParsePrefix("100.64.0.0/24")), @@ -740,7 +745,8 @@ func TestACLNamedHostsCanReach(t *testing.T) { for name, testCase := range tests { t.Run(name, func(t *testing.T) { - scenario := aclScenario(t, + scenario := aclScenario( + t, &testCase.policy, "acl-namedreach", 2, @@ -1118,7 +1124,8 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) { func TestACLAutogroupMember(t *testing.T) { IntegrationSkip(t) - scenario := aclScenario(t, + scenario := aclScenario( + t, &policyv2.Policy{ ACLs: []policyv2.ACL{ { @@ -1232,10 +1239,10 @@ func TestACLAutogroupTagged(t *testing.T) { require.NoError(t, err) // Create two pre-auth keys per user: one tagged, one untagged - taggedAuthKey, err := scenario.CreatePreAuthKeyWithTags(user.GetId(), true, false, []string{"tag:test"}) + taggedAuthKey, err := scenario.CreatePreAuthKeyWithTags(user.GetID().Or(0), true, false, []string{"tag:test"}) require.NoError(t, err) - untaggedAuthKey, err := scenario.CreatePreAuthKey(user.GetId(), true, false) + untaggedAuthKey, err := scenario.CreatePreAuthKey(user.GetID().Or(0), true, false) require.NoError(t, err) // Create nodes with proper naming @@ -1247,13 +1254,13 @@ func TestACLAutogroupTagged(t *testing.T) { if i == 0 { // First node is tagged - use tagged PreAuthKey - authKey = taggedAuthKey.GetKey() + authKey = taggedAuthKey.GetKey().Or("") version = "head" t.Logf("Creating tagged node for %s", userStr) } else { // Second node is untagged - use untagged PreAuthKey - authKey = untaggedAuthKey.GetKey() + authKey = untaggedAuthKey.GetKey().Or("") version = "unstable" t.Logf("Creating untagged node for %s", userStr) @@ -1554,7 +1561,7 @@ func TestACLAutogroupSelf(t *testing.T) { require.NoError(t, err) // Create a tagged PreAuthKey for the router node (tags-as-identity model) - authKey, err := scenario.CreatePreAuthKeyWithTags(routerUser.GetId(), true, false, []string{"tag:router-node"}) + authKey, err := scenario.CreatePreAuthKeyWithTags(routerUser.GetID().Or(0), true, false, []string{"tag:router-node"}) require.NoError(t, err) // Create router node (tags come from the PreAuthKey). @@ -1577,7 +1584,7 @@ func TestACLAutogroupSelf(t *testing.T) { err = routerClient.WaitForNeedsLogin(integrationutil.PeerSyncTimeout()) require.NoError(t, err) - err = routerClient.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = routerClient.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) err = routerClient.WaitForRunning(integrationutil.PeerSyncTimeout()) @@ -1947,7 +1954,8 @@ func TestACLPolicyPropagationOverTime(t *testing.T) { networks := scenario.Networks() require.NotEmpty(t, networks, "scenario should have at least one network") - newClient := scenario.MustAddAndLoginClient(t, "user1", "all", headscale, + newClient := scenario.MustAddAndLoginClient( + t, "user1", "all", headscale, tsic.WithNetfilter("off"), tsic.WithPackages("curl"), tsic.WithWebserver(80), @@ -2008,7 +2016,7 @@ func TestACLPolicyPropagationOverTime(t *testing.T) { // Get the node list and find the newest node (highest ID) var ( - nodeList []*v1.Node + nodeList []*apiv1.Node nodeToDeleteID uint64 ) @@ -2019,8 +2027,8 @@ func TestACLPolicyPropagationOverTime(t *testing.T) { // Find the node with the highest ID (the newest one) for _, node := range nodeList { - if node.GetId() > nodeToDeleteID { - nodeToDeleteID = node.GetId() + if node.GetID().Or(0) > nodeToDeleteID { + nodeToDeleteID = node.GetID().Or(0) } } }, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "iteration %d: Phase 2b - listing nodes before deletion", iteration) @@ -2205,7 +2213,7 @@ func TestACLTagPropagation(t *testing.T) { nodes, err := headscale.ListNodes("user1") require.NoError(t, err) - return user2Clients[0], user1Clients[0], nodes[0].GetId() + return user2Clients[0], user1Clients[0], nodes[0].GetID().Or(0) }, initialAccess: false, // user2 cannot access user1 (no tag) tagChange: []string{"tag:shared"}, // add tag:shared @@ -2256,7 +2264,7 @@ func TestACLTagPropagation(t *testing.T) { // Create user1's node WITH tag:shared via PreAuthKey taggedKey, err := scenario.CreatePreAuthKeyWithTags( - userMap["user1"].GetId(), false, false, []string{"tag:shared"}, + userMap["user1"].GetID().Or(0), false, false, []string{"tag:shared"}, ) require.NoError(t, err) @@ -2269,11 +2277,11 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey()) + err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey().Or("")) require.NoError(t, err) // Create user2's node (untagged) - untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetId(), false, false) + untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetID().Or(0), false, false) require.NoError(t, err) user2Node, err := scenario.CreateTailscaleNode( @@ -2285,7 +2293,7 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey()) + err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey().Or("")) require.NoError(t, err) err = scenario.WaitForTailscaleSync() @@ -2295,10 +2303,10 @@ func TestACLTagPropagation(t *testing.T) { allNodes, err := headscale.ListNodes() require.NoError(t, err) - tagged := findNode(allNodes, func(n *v1.Node) bool { return len(n.GetTags()) > 0 }) + tagged := findNode(allNodes, func(n *apiv1.Node) bool { return len(n.GetTags()) > 0 }) require.NotNil(t, tagged, "expected a tagged node") - return user2Node, user1Node, tagged.GetId() + return user2Node, user1Node, tagged.GetID().Or(0) }, initialAccess: true, // user2 can access user1 (has tag:shared) tagChange: []string{"tag:other"}, // replace with tag:other @@ -2349,7 +2357,7 @@ func TestACLTagPropagation(t *testing.T) { // Create user1's node with tag:team-a (user2 has NO ACL for this) taggedKey, err := scenario.CreatePreAuthKeyWithTags( - userMap["user1"].GetId(), false, false, []string{"tag:team-a"}, + userMap["user1"].GetID().Or(0), false, false, []string{"tag:team-a"}, ) require.NoError(t, err) @@ -2362,11 +2370,11 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey()) + err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey().Or("")) require.NoError(t, err) // Create user2's node - untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetId(), false, false) + untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetID().Or(0), false, false) require.NoError(t, err) user2Node, err := scenario.CreateTailscaleNode( @@ -2378,7 +2386,7 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey()) + err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey().Or("")) require.NoError(t, err) err = scenario.WaitForTailscaleSync() @@ -2388,10 +2396,10 @@ func TestACLTagPropagation(t *testing.T) { allNodes, err := headscale.ListNodes() require.NoError(t, err) - tagged := findNode(allNodes, func(n *v1.Node) bool { return len(n.GetTags()) > 0 }) + tagged := findNode(allNodes, func(n *apiv1.Node) bool { return len(n.GetTags()) > 0 }) require.NotNil(t, tagged, "expected a tagged node") - return user2Node, user1Node, tagged.GetId() + return user2Node, user1Node, tagged.GetID().Or(0) }, initialAccess: false, // user2 cannot access (tag:team-a not in ACL) tagChange: []string{"tag:team-b"}, // change to tag:team-b @@ -2442,7 +2450,7 @@ func TestACLTagPropagation(t *testing.T) { // Create user1's node with BOTH tags taggedKey, err := scenario.CreatePreAuthKeyWithTags( - userMap["user1"].GetId(), false, false, []string{"tag:web", "tag:internal"}, + userMap["user1"].GetID().Or(0), false, false, []string{"tag:web", "tag:internal"}, ) require.NoError(t, err) @@ -2455,11 +2463,11 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey()) + err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey().Or("")) require.NoError(t, err) // Create user2's node - untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetId(), false, false) + untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetID().Or(0), false, false) require.NoError(t, err) user2Node, err := scenario.CreateTailscaleNode( @@ -2471,7 +2479,7 @@ func TestACLTagPropagation(t *testing.T) { tsic.WithNetfilter("off"), ) require.NoError(t, err) - err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey()) + err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey().Or("")) require.NoError(t, err) err = scenario.WaitForTailscaleSync() @@ -2481,10 +2489,10 @@ func TestACLTagPropagation(t *testing.T) { allNodes, err := headscale.ListNodes() require.NoError(t, err) - tagged := findNode(allNodes, func(n *v1.Node) bool { return len(n.GetTags()) > 0 }) + tagged := findNode(allNodes, func(n *apiv1.Node) bool { return len(n.GetTags()) > 0 }) require.NotNil(t, tagged, "expected a tagged node") - return user2Node, user1Node, tagged.GetId() + return user2Node, user1Node, tagged.GetID().Or(0) }, initialAccess: true, // user2 can access (has tag:web) tagChange: []string{"tag:internal"}, // remove tag:web, keep tag:internal @@ -2535,7 +2543,7 @@ func TestACLTagPropagation(t *testing.T) { nodes, err := headscale.ListNodes("user1") require.NoError(t, err) - return user2Clients[0], user1Clients[0], nodes[0].GetId() + return user2Clients[0], user1Clients[0], nodes[0].GetID().Or(0) }, initialAccess: false, // user2 cannot access user1 (no tag yet) tagChange: []string{"tag:server"}, // assign tag:server @@ -2616,7 +2624,7 @@ func TestACLTagPropagation(t *testing.T) { allNodes, err := headscale.ListNodes() assert.NoError(c, err) - node := findNode(allNodes, func(n *v1.Node) bool { return n.GetId() == targetNodeID }) + node := findNode(allNodes, func(n *apiv1.Node) bool { return n.GetID().Or(0) == targetNodeID }) assert.NotNil(c, node, "Node should still exist") if node != nil { @@ -2759,7 +2767,7 @@ func TestACLTagPropagationPortSpecific(t *testing.T) { // Create user1's node WITH tag:webserver taggedKey, err := scenario.CreatePreAuthKeyWithTags( - userMap["user1"].GetId(), false, false, []string{"tag:webserver"}, + userMap["user1"].GetID().Or(0), false, false, []string{"tag:webserver"}, ) require.NoError(t, err) @@ -2773,11 +2781,11 @@ func TestACLTagPropagationPortSpecific(t *testing.T) { ) require.NoError(t, err) - err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey()) + err = user1Node.Login(headscale.GetEndpoint(), taggedKey.GetKey().Or("")) require.NoError(t, err) // Create user2's node - untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetId(), false, false) + untaggedKey, err := scenario.CreatePreAuthKey(userMap["user2"].GetID().Or(0), false, false) require.NoError(t, err) user2Node, err := scenario.CreateTailscaleNode( @@ -2789,7 +2797,7 @@ func TestACLTagPropagationPortSpecific(t *testing.T) { ) require.NoError(t, err) - err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey()) + err = user2Node.Login(headscale.GetEndpoint(), untaggedKey.GetKey().Or("")) require.NoError(t, err) err = scenario.WaitForTailscaleSync() @@ -2799,10 +2807,10 @@ func TestACLTagPropagationPortSpecific(t *testing.T) { allNodes, err := headscale.ListNodes() require.NoError(t, err) - tagged := findNode(allNodes, func(n *v1.Node) bool { return len(n.GetTags()) > 0 }) + tagged := findNode(allNodes, func(n *apiv1.Node) bool { return len(n.GetTags()) > 0 }) require.NotNil(t, tagged, "expected a tagged node") - targetNodeID := tagged.GetId() + targetNodeID := tagged.GetID().Or(0) targetFQDN, err := user1Node.FQDN() require.NoError(t, err) @@ -2828,7 +2836,7 @@ func TestACLTagPropagationPortSpecific(t *testing.T) { allNodes, err := headscale.ListNodes() assert.NoError(c, err) //nolint:testifylint // CollectT requires assert - node := findNode(allNodes, func(n *v1.Node) bool { return n.GetId() == targetNodeID }) + node := findNode(allNodes, func(n *apiv1.Node) bool { return n.GetID().Or(0) == targetNodeID }) assert.NotNil(c, node, "Node should still exist") if node != nil { @@ -3070,7 +3078,7 @@ func TestACLGroupAfterUserDeletion(t *testing.T) { nodes, err := headscale.ListNodes("user3") require.NoError(t, err) require.Len(t, nodes, 1, "user3 should have exactly one node") - user3NodeID := nodes[0].GetId() + user3NodeID := nodes[0].GetID().Or(0) // Delete user3's node first (required before deleting the user) err = headscale.DeleteNode(user3NodeID) @@ -3081,7 +3089,7 @@ func TestACLGroupAfterUserDeletion(t *testing.T) { require.NoError(t, err, "user3 should exist") // Now delete user3 (after their nodes are deleted) - err = headscale.DeleteUser(user3.GetId()) + err = headscale.DeleteUser(user3.GetID().Or(0)) require.NoError(t, err) // Verify user3 is deleted @@ -3249,13 +3257,13 @@ func TestACLGroupDeletionExactReproduction(t *testing.T) { nodes, err := headscale.ListNodes(userToDelete) require.NoError(t, err) require.Len(t, nodes, 1) - err = headscale.DeleteNode(nodes[0].GetId()) + err = headscale.DeleteNode(nodes[0].GetID().Or(0)) require.NoError(t, err) userToDeleteObj, err := GetUserByName(headscale, userToDelete) require.NoError(t, err, "user to delete should exist") - err = headscale.DeleteUser(userToDeleteObj.GetId()) + err = headscale.DeleteUser(userToDeleteObj.GetID().Or(0)) require.NoError(t, err) t.Log("Step 2: DONE - user2 deleted, ACL still has user2@ reference") diff --git a/integration/api_auth_test.go b/integration/api_auth_test.go index 8dfa6fd5..cc7a0b45 100644 --- a/integration/api_auth_test.go +++ b/integration/api_auth_test.go @@ -11,13 +11,12 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/integrationutil" "github.com/juanfont/headscale/integration/tsic" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/encoding/protojson" ) // TestAPIAuthenticationBypass tests that the API authentication middleware @@ -215,11 +214,11 @@ func TestAPIAuthenticationBypass(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected 200 status code with valid API key") - // Should be able to parse as protobuf JSON - var response v1.ListUsersResponse + // Should be able to parse as the ogen JSON list envelope + var response apiv1.ListUsersOK - err = protojson.Unmarshal(body, &response) - require.NoError(t, err, "Response should be valid protobuf JSON with valid API key") + err = json.Unmarshal(body, &response) + require.NoError(t, err, "Response should be valid JSON with valid API key") // Should contain our test users users := response.GetUsers() @@ -227,7 +226,7 @@ func TestAPIAuthenticationBypass(t *testing.T) { userNames := make([]string, len(users)) for i, u := range users { - userNames[i] = u.GetName() + userNames[i] = u.GetName().Or("") } assert.Contains(t, userNames, "user1") @@ -406,10 +405,10 @@ func TestAPIAuthenticationBypassCurl(t *testing.T) { "Curl with valid API key should return 200") // Should contain user data - var response v1.ListUsersResponse + var response apiv1.ListUsersOK - err = protojson.Unmarshal([]byte(responseBody), &response) - require.NoError(t, err, "Response should be valid protobuf JSON") + err = json.Unmarshal([]byte(responseBody), &response) + require.NoError(t, err, "Response should be valid JSON") users := response.GetUsers() assert.Len(t, users, 2, "Should have 2 users") @@ -522,9 +521,9 @@ func TestGRPCAuthenticationBypass(t *testing.T) { require.NoError(t, err, "gRPC connection with valid API key should succeed, output: %s", output) - // CLI outputs the users array directly, not wrapped in [v1.ListUsersResponse] + // CLI outputs the users array directly as JSON // Parse as JSON array (CLI uses [json.Marshal], not protojson) - var users []*v1.User + var users []*apiv1.User err = json.Unmarshal([]byte(output), &users) require.NoError(t, err, "Response should be valid JSON array") @@ -532,7 +531,7 @@ func TestGRPCAuthenticationBypass(t *testing.T) { userNames := make([]string, len(users)) for i, u := range users { - userNames[i] = u.GetName() + userNames[i] = u.GetName().Or("") } assert.Contains(t, userNames, "grpcuser1") @@ -681,9 +680,9 @@ cli: require.NoError(t, err, "CLI with valid API key should succeed") - // CLI outputs the users array directly, not wrapped in [v1.ListUsersResponse] + // CLI outputs the users array directly as JSON // Parse as JSON array (CLI uses [json.Marshal], not protojson) - var users []*v1.User + var users []*apiv1.User err = json.Unmarshal([]byte(output), &users) require.NoError(t, err, "Response should be valid JSON array") @@ -691,7 +690,7 @@ cli: userNames := make([]string, len(users)) for i, u := range users { - userNames[i] = u.GetName() + userNames[i] = u.GetName().Or("") } assert.Contains(t, userNames, "cliuser1") diff --git a/integration/auth_key_test.go b/integration/auth_key_test.go index 7aa928c5..aa7c59c0 100644 --- a/integration/auth_key_test.go +++ b/integration/auth_key_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" @@ -74,7 +74,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) { } var ( - listNodes []*v1.Node + listNodes []*apiv1.Node nodeCountBeforeLogout int ) @@ -135,12 +135,12 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) { require.NoError(t, err) for _, userName := range spec.Users { - key, err := scenario.CreatePreAuthKey(userMap[userName].GetId(), true, false) + key, err := scenario.CreatePreAuthKey(userMap[userName].GetID().Or(0), true, false) if err != nil { t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } - err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey().Or("")) if err != nil { t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } @@ -232,7 +232,8 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { defer scenario.ShutdownAssertNoPanics(t) - err = scenario.CreateHeadscaleEnv([]tsic.Option{}, + err = scenario.CreateHeadscaleEnv( + []tsic.Option{}, hsic.WithTestName("keyrelognewuser"), ) requireNoErrHeadscaleEnv(t, err) @@ -256,7 +257,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { requireAllClientsNetInfoAndDERP(t, headscale, expectedNodes, "all clients should have NetInfo and DERP after initial login", 3*time.Minute) var ( - listNodes []*v1.Node + listNodes []*apiv1.Node nodeCountBeforeLogout int ) @@ -290,7 +291,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { require.NoError(t, err) // Create a new authkey for user1, to be used for all clients - key, err := scenario.CreatePreAuthKey(userMap["user1"].GetId(), true, false) + key, err := scenario.CreatePreAuthKey(userMap["user1"].GetID().Or(0), true, false) if err != nil { t.Fatalf("failed to create pre-auth key for user1: %s", err) } @@ -298,13 +299,13 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { // Log in all clients as user1, iterating over the spec only returns the // clients, not the usernames. for _, userName := range spec.Users { - err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey().Or("")) if err != nil { t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } } - var user1Nodes []*v1.Node + var user1Nodes []*apiv1.Node t.Logf("Validating user1 node count after relogin at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -318,7 +319,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { // Collect expected node IDs for user1 after relogin expectedUser1Nodes := make([]types.NodeID, 0, len(user1Nodes)) for _, node := range user1Nodes { - expectedUser1Nodes = append(expectedUser1Nodes, types.NodeID(node.GetId())) + expectedUser1Nodes = append(expectedUser1Nodes, types.NodeID(node.GetID().Or(0))) } // Validate connection state after relogin as user1 @@ -328,7 +329,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) { // Validate that user2 still has their original nodes after user1's re-authentication // When nodes re-authenticate with a different user's pre-auth key, NEW nodes are created // for the new user. The original nodes remain with the original user. - var user2Nodes []*v1.Node + var user2Nodes []*apiv1.Node t.Logf("Validating user2 node persistence after user1 relogin at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -402,7 +403,7 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) { requireAllClientsNetInfoAndDERP(t, headscale, expectedNodes, "all clients should have NetInfo and DERP after initial login", 3*time.Minute) var ( - listNodes []*v1.Node + listNodes []*apiv1.Node nodeCountBeforeLogout int ) @@ -446,7 +447,7 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) { require.NoError(t, err) for _, userName := range spec.Users { - key, err := scenario.CreatePreAuthKey(userMap[userName].GetId(), true, false) + key, err := scenario.CreatePreAuthKey(userMap[userName].GetID().Or(0), true, false) if err != nil { t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } @@ -458,12 +459,13 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) { "preauthkeys", "expire", "--id", - strconv.FormatUint(key.GetId(), 10), - }) + strconv.FormatUint(key.GetID().Or(0), 10), + }, + ) require.NoError(t, err) require.NoError(t, err) - err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey().Or("")) assert.ErrorContains(t, err, "authkey expired") } }) @@ -498,14 +500,14 @@ func TestAuthKeyDeleteKey(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap["user1"].GetId() + userID := userMap["user1"].GetID().Or(0) // Create a pre-auth key - we keep the full key string before it gets redacted authKey, err := scenario.CreatePreAuthKey(userID, false, false) require.NoError(t, err) - authKeyString := authKey.GetKey() - authKeyID := authKey.GetId() + authKeyString := authKey.GetKey().Or("") + authKeyID := authKey.GetID().Or(0) t.Logf("Created pre-auth key ID %d: %s", authKeyID, authKeyString) // Create a tailscale client and log it in with the auth key @@ -519,7 +521,7 @@ func TestAuthKeyDeleteKey(t *testing.T) { require.NoError(t, err) // Wait for the node to be registered - var user1Nodes []*v1.Node + var user1Nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { var err error @@ -529,8 +531,8 @@ func TestAuthKeyDeleteKey(t *testing.T) { assert.Len(c, user1Nodes, 1) }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for node to be registered") - nodeID := user1Nodes[0].GetId() - nodeName := user1Nodes[0].GetName() + nodeID := user1Nodes[0].GetID().Or(0) + nodeName := user1Nodes[0].GetName().Or("") t.Logf("Node %d (%s) created successfully with auth_key_id=%d", nodeID, nodeName, authKeyID) // Verify node is online @@ -640,7 +642,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) { // Step 1: Verify initial route is advertised, approved, and SERVING t.Logf("Step 1: Verifying initial route is advertised, approved, and SERVING at %s", time.Now().Format(TimestampFormat)) - var initialNode *v1.Node + var initialNode *apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err := headscale.ListNodes() @@ -662,7 +664,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) { }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "initial route should be serving") require.NotNil(t, initialNode, "Initial node should be found") - initialNodeID := initialNode.GetId() + initialNodeID := initialNode.GetID().Or(0) t.Logf("Initial node ID: %d, Available: %v, Approved: %v, Serving: %v", initialNodeID, initialNode.GetAvailableRoutes(), initialNode.GetApprovedRoutes(), initialNode.GetSubnetRoutes()) @@ -694,12 +696,12 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - key, err := scenario.CreatePreAuthKey(userMap[user].GetId(), true, false) + key, err := scenario.CreatePreAuthKey(userMap[user].GetID().Or(0), true, false) require.NoError(t, err) // Re-login - the container already has extraLoginArgs with --advertise-routes // from the initial setup, so routes will be advertised on re-login - err = scenario.RunTailscaleUp(user, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(user, headscale.GetEndpoint(), key.GetKey().Or("")) require.NoError(t, err) // Wait for client to be running @@ -738,7 +740,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) { "BUG #2896: Subnet routes should contain %s after relogin", advertiseRoute) // Also verify node ID was preserved (same node, not new registration) - assert.Equal(c, initialNodeID, node.GetId(), + assert.Equal(c, initialNodeID, node.GetID().Or(0), "Node ID should be preserved after same-user relogin") } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, diff --git a/integration/auth_oidc_test.go b/integration/auth_oidc_test.go index 96bd980e..7cdfc88a 100644 --- a/integration/auth_oidc_test.go +++ b/integration/auth_oidc_test.go @@ -11,7 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" @@ -86,38 +86,38 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { listUsers, err := headscale.ListUsers() require.NoError(t, err) - want := []*v1.User{ + want := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@test.no", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@test.no"), }, { - Id: 2, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, { - Id: 3, - Name: "user2", - Email: "user2@test.no", + ID: apiv1.NewOptUint64(3), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@test.no"), }, { - Id: 4, - Name: "user2", - Email: "", // Unverified - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user2", + ID: apiv1.NewOptUint64(4), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString(""), // Unverified + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user2"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { t.Fatalf("unexpected users: %s", diff) } } @@ -245,38 +245,38 @@ func TestOIDC024UserCreation(t *testing.T) { emailVerified bool cliUsers []string oidcUsers []string - want func(iss string) []*v1.User + want func(iss string) []*apiv1.User }{ { name: "no-migration-verified-email", emailVerified: true, cliUsers: []string{"user1", "user2"}, oidcUsers: []string{"user1", "user2"}, - want: func(iss string) []*v1.User { - return []*v1.User{ + want: func(iss string) []*apiv1.User { + return []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@test.no", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@test.no"), }, { - Id: 2, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: iss + "/user1", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user1"), }, { - Id: 3, - Name: "user2", - Email: "user2@test.no", + ID: apiv1.NewOptUint64(3), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@test.no"), }, { - Id: 4, - Name: "user2", - Email: "user2@headscale.net", - Provider: "oidc", - ProviderId: iss + "/user2", + ID: apiv1.NewOptUint64(4), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user2"), }, } }, @@ -286,29 +286,29 @@ func TestOIDC024UserCreation(t *testing.T) { emailVerified: false, cliUsers: []string{"user1", "user2"}, oidcUsers: []string{"user1", "user2"}, - want: func(iss string) []*v1.User { - return []*v1.User{ + want: func(iss string) []*apiv1.User { + return []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@test.no", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@test.no"), }, { - Id: 2, - Name: "user1", - Provider: "oidc", - ProviderId: iss + "/user1", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user1"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user1"), }, { - Id: 3, - Name: "user2", - Email: "user2@test.no", + ID: apiv1.NewOptUint64(3), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@test.no"), }, { - Id: 4, - Name: "user2", - Provider: "oidc", - ProviderId: iss + "/user2", + ID: apiv1.NewOptUint64(4), + Name: apiv1.NewOptString("user2"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user2"), }, } }, @@ -318,29 +318,29 @@ func TestOIDC024UserCreation(t *testing.T) { emailVerified: false, cliUsers: []string{"user1.headscale.net", "user2.headscale.net"}, oidcUsers: []string{"user1", "user2"}, - want: func(iss string) []*v1.User { - return []*v1.User{ + want: func(iss string) []*apiv1.User { + return []*apiv1.User{ { - Id: 1, - Name: "user1.headscale.net", - Email: "user1.headscale.net@test.no", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1.headscale.net"), + Email: apiv1.NewOptString("user1.headscale.net@test.no"), }, { - Id: 2, - Name: "user1", - Provider: "oidc", - ProviderId: iss + "/user1", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user1"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user1"), }, { - Id: 3, - Name: "user2.headscale.net", - Email: "user2.headscale.net@test.no", + ID: apiv1.NewOptUint64(3), + Name: apiv1.NewOptString("user2.headscale.net"), + Email: apiv1.NewOptString("user2.headscale.net@test.no"), }, { - Id: 4, - Name: "user2", - Provider: "oidc", - ProviderId: iss + "/user2", + ID: apiv1.NewOptUint64(4), + Name: apiv1.NewOptString("user2"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(iss + "/user2"), }, } }, @@ -393,10 +393,10 @@ func TestOIDC024UserCreation(t *testing.T) { require.NoError(t, err) sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { t.Errorf("unexpected users: %s", diff) } }) @@ -509,28 +509,28 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { assert.NoError(ct, err, "Failed to list users during initial validation") assert.Len(ct, listUsers, 1, "Expected exactly 1 user after first login, got %d", len(listUsers)) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after first login - unexpected users: %s", diff) } }, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 creation after initial OIDC login") t.Logf("Validating initial node creation at %s", time.Now().Format(TimestampFormat)) - var listNodes []*v1.Node + var listNodes []*apiv1.Node assert.EventuallyWithT(t, func(ct *assert.CollectT) { var err error @@ -593,33 +593,33 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { assert.NoError(ct, err, "Failed to list users after user2 login") assert.Len(ct, listUsers, 2, "Expected exactly 2 users after user2 login, got %d users", len(listUsers)) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, { - Id: 2, - Name: "user2", - Email: "user2@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user2", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user2"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after user2 login - expected both user1 and user2: %s", diff) } }, integrationutil.StatusReadyTimeout, 1*time.Second, "validating both user1 and user2 exist after second OIDC login") - var listNodesAfterNewUserLogin []*v1.Node + var listNodesAfterNewUserLogin []*apiv1.Node // First, wait for the new node to be created t.Logf("Waiting for user2 node creation at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -640,9 +640,9 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { if len(listNodesAfterNewUserLogin) >= 2 { // Machine key is the same as the "machine" has not changed, // but Node key is not as it is a new node - assert.Equal(ct, listNodes[0].GetMachineKey(), listNodesAfterNewUserLogin[0].GetMachineKey(), "Machine key should be preserved from original node") - assert.Equal(ct, listNodesAfterNewUserLogin[0].GetMachineKey(), listNodesAfterNewUserLogin[1].GetMachineKey(), "Both nodes should share the same machine key") - assert.NotEqual(ct, listNodesAfterNewUserLogin[0].GetNodeKey(), listNodesAfterNewUserLogin[1].GetNodeKey(), "Node keys should be different between user1 and user2 nodes") + assert.Equal(ct, listNodes[0].GetMachineKey().Or(""), listNodesAfterNewUserLogin[0].GetMachineKey().Or(""), "Machine key should be preserved from original node") + assert.Equal(ct, listNodesAfterNewUserLogin[0].GetMachineKey().Or(""), listNodesAfterNewUserLogin[1].GetMachineKey().Or(""), "Both nodes should share the same machine key") + assert.NotEqual(ct, listNodesAfterNewUserLogin[0].GetNodeKey().Or(""), listNodesAfterNewUserLogin[1].GetNodeKey().Or(""), "Node keys should be different between user1 and user2 nodes") } }, integrationutil.PolicyPropagationTimeout, 2*time.Second, "waiting for node count stabilization at exactly 2 nodes after user2 login") @@ -650,9 +650,9 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { var activeUser2NodeID types.NodeID for _, node := range listNodesAfterNewUserLogin { - if node.GetUser().GetId() == 2 { // user2 - activeUser2NodeID = types.NodeID(node.GetId()) - t.Logf("Active user2 node: %d (User: %s)", node.GetId(), node.GetUser().GetName()) + if node.GetUser().Value.ID.Or(0) == 2 { // user2 + activeUser2NodeID = types.NodeID(node.GetID().Or(0)) + t.Logf("Active user2 node: %d (User: %s)", node.GetID().Or(0), node.GetUser().Value.Name.Or("")) break } @@ -685,9 +685,9 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { // Validate node stability - ensure no phantom nodes for i, node := range currentNodes { - assert.NotNil(ct, node.GetUser(), "Node %d should have a valid user before logout", i) - assert.NotEmpty(ct, node.GetMachineKey(), "Node %d should have a valid machine key before logout", i) - t.Logf("Pre-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().GetName(), node.GetMachineKey()[:16]+"...") + assert.True(ct, node.GetUser().IsSet(), "Node %d should have a valid user before logout", i) + assert.NotEmpty(ct, node.GetMachineKey().Or(""), "Node %d should have a valid machine key before logout", i) + t.Logf("Pre-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().Value.Name.Or(""), node.GetMachineKey().Or("")[:16]+"...") } }, integrationutil.HAConvergeTimeout, 2*time.Second, "validating stable node count and integrity before user2 logout") @@ -730,9 +730,9 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { // Ensure both nodes are still valid (not cleaned up incorrectly) for i, node := range currentNodes { - assert.NotNil(ct, node.GetUser(), "Node %d should still have a valid user after user2 logout", i) - assert.NotEmpty(ct, node.GetMachineKey(), "Node %d should still have a valid machine key after user2 logout", i) - t.Logf("Post-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().GetName(), node.GetMachineKey()[:16]+"...") + assert.True(ct, node.GetUser().IsSet(), "Node %d should still have a valid user after user2 logout", i) + assert.NotEmpty(ct, node.GetMachineKey().Or(""), "Node %d should still have a valid machine key after user2 logout", i) + t.Logf("Post-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().Value.Name.Or(""), node.GetMachineKey().Or("")[:16]+"...") } }, integrationutil.HAConvergeTimeout, 2*time.Second, "validating node persistence and integrity after user2 logout") @@ -761,33 +761,33 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { assert.NoError(ct, err, "Failed to list users during final validation") assert.Len(ct, listUsers, 2, "Should still have exactly 2 users after user1 relogin, got %d", len(listUsers)) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, { - Id: 2, - Name: "user2", - Email: "user2@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user2", + ID: apiv1.NewOptUint64(2), + Name: apiv1.NewOptString("user2"), + Email: apiv1.NewOptString("user2@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user2"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { ct.Errorf("Final user validation failed - both users should persist after relogin cycle: %s", diff) } }, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user persistence after complete relogin cycle (user1->user2->user1)") - var listNodesAfterLoggingBackIn []*v1.Node + var listNodesAfterLoggingBackIn []*apiv1.Node // Wait for login to complete and nodes to stabilize t.Logf("Final node validation: checking node stability after user1 relogin at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -806,24 +806,24 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { // Validate that the machine we had when we logged in the first time, has the same // machine key, but a different ID than the newly logged in version of the same // machine. - assert.Equal(ct, listNodes[0].GetMachineKey(), listNodesAfterNewUserLogin[0].GetMachineKey(), "Original user1 machine key should match user1 node after user switch") - assert.Equal(ct, listNodes[0].GetNodeKey(), listNodesAfterNewUserLogin[0].GetNodeKey(), "Original user1 node key should match user1 node after user switch") - assert.Equal(ct, listNodes[0].GetId(), listNodesAfterNewUserLogin[0].GetId(), "Original user1 node ID should match user1 node after user switch") - assert.Equal(ct, listNodes[0].GetMachineKey(), listNodesAfterNewUserLogin[1].GetMachineKey(), "User1 and user2 nodes should share the same machine key") - assert.NotEqual(ct, listNodes[0].GetId(), listNodesAfterNewUserLogin[1].GetId(), "User1 and user2 nodes should have different node IDs") - assert.NotEqual(ct, listNodes[0].GetUser().GetId(), listNodesAfterNewUserLogin[1].GetUser().GetId(), "User1 and user2 nodes should belong to different users") + assert.Equal(ct, listNodes[0].GetMachineKey().Or(""), listNodesAfterNewUserLogin[0].GetMachineKey().Or(""), "Original user1 machine key should match user1 node after user switch") + assert.Equal(ct, listNodes[0].GetNodeKey().Or(""), listNodesAfterNewUserLogin[0].GetNodeKey().Or(""), "Original user1 node key should match user1 node after user switch") + assert.Equal(ct, listNodes[0].GetID().Or(0), listNodesAfterNewUserLogin[0].GetID().Or(0), "Original user1 node ID should match user1 node after user switch") + assert.Equal(ct, listNodes[0].GetMachineKey().Or(""), listNodesAfterNewUserLogin[1].GetMachineKey().Or(""), "User1 and user2 nodes should share the same machine key") + assert.NotEqual(ct, listNodes[0].GetID().Or(0), listNodesAfterNewUserLogin[1].GetID().Or(0), "User1 and user2 nodes should have different node IDs") + assert.NotEqual(ct, listNodes[0].GetUser().Value.ID.Or(0), listNodesAfterNewUserLogin[1].GetUser().Value.ID.Or(0), "User1 and user2 nodes should belong to different users") // Even tho we are logging in again with the same user, the previous key has been expired // and a new one has been generated. The node entry in the database should be the same // as the user + machinekey still matches. - assert.Equal(ct, listNodes[0].GetMachineKey(), listNodesAfterLoggingBackIn[0].GetMachineKey(), "Machine key should remain consistent after user1 relogin") - assert.NotEqual(ct, listNodes[0].GetNodeKey(), listNodesAfterLoggingBackIn[0].GetNodeKey(), "Node key should be regenerated after user1 relogin") - assert.Equal(ct, listNodes[0].GetId(), listNodesAfterLoggingBackIn[0].GetId(), "Node ID should be preserved for user1 after relogin") + assert.Equal(ct, listNodes[0].GetMachineKey().Or(""), listNodesAfterLoggingBackIn[0].GetMachineKey().Or(""), "Machine key should remain consistent after user1 relogin") + assert.NotEqual(ct, listNodes[0].GetNodeKey().Or(""), listNodesAfterLoggingBackIn[0].GetNodeKey().Or(""), "Node key should be regenerated after user1 relogin") + assert.Equal(ct, listNodes[0].GetID().Or(0), listNodesAfterLoggingBackIn[0].GetID().Or(0), "Node ID should be preserved for user1 after relogin") // The "logged back in" machine should have the same machinekey but a different nodekey // than the version logged in with a different user. - assert.Equal(ct, listNodesAfterLoggingBackIn[0].GetMachineKey(), listNodesAfterLoggingBackIn[1].GetMachineKey(), "Both final nodes should share the same machine key") - assert.NotEqual(ct, listNodesAfterLoggingBackIn[0].GetNodeKey(), listNodesAfterLoggingBackIn[1].GetNodeKey(), "Final nodes should have different node keys for different users") + assert.Equal(ct, listNodesAfterLoggingBackIn[0].GetMachineKey().Or(""), listNodesAfterLoggingBackIn[1].GetMachineKey().Or(""), "Both final nodes should share the same machine key") + assert.NotEqual(ct, listNodesAfterLoggingBackIn[0].GetNodeKey().Or(""), listNodesAfterLoggingBackIn[1].GetNodeKey().Or(""), "Final nodes should have different node keys for different users") t.Logf("Final validation complete - node counts and key relationships verified at %s", time.Now().Format(TimestampFormat)) }, integrationutil.HAConvergeTimeout, 2*time.Second, "validating final node state after complete user1->user2->user1 relogin cycle with detailed key validation") @@ -832,9 +832,9 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { var activeUser1NodeID types.NodeID for _, node := range listNodesAfterLoggingBackIn { - if node.GetUser().GetId() == 1 { // user1 - activeUser1NodeID = types.NodeID(node.GetId()) - t.Logf("Active user1 node after relogin: %d (User: %s)", node.GetId(), node.GetUser().GetName()) + if node.GetUser().Value.ID.Or(0) == 1 { // user1 + activeUser1NodeID = types.NodeID(node.GetID().Or(0)) + t.Logf("Active user1 node after relogin: %d (User: %s)", node.GetID().Or(0), node.GetUser().Value.Name.Or("")) break } @@ -942,27 +942,27 @@ func TestOIDCFollowUpUrl(t *testing.T) { require.NoError(t, err) assert.Len(t, listUsers, 1) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, } sort.Slice( listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }, ) if diff := cmp.Diff( wantUsers, listUsers, - cmpopts.IgnoreUnexported(v1.User{}), - cmpopts.IgnoreFields(v1.User{}, "CreatedAt"), + cmpopts.IgnoreUnexported(apiv1.User{}), + cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt"), ); diff != "" { t.Fatalf("unexpected users: %s", diff) } @@ -1051,27 +1051,27 @@ func TestOIDCMultipleOpenedLoginUrls(t *testing.T) { require.NoError(t, err) assert.Len(t, listUsers, 1) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, } sort.Slice( listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }, ) if diff := cmp.Diff( wantUsers, listUsers, - cmpopts.IgnoreUnexported(v1.User{}), - cmpopts.IgnoreFields(v1.User{}, "CreatedAt"), + cmpopts.IgnoreUnexported(apiv1.User{}), + cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt"), ); diff != "" { t.Fatalf("unexpected users: %s", diff) } @@ -1159,28 +1159,28 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { assert.NoError(ct, err, "Failed to list users during initial validation") assert.Len(ct, listUsers, 1, "Expected exactly 1 user after first login, got %d", len(listUsers)) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { ct.Errorf("User validation failed after first login - unexpected users: %s", diff) } }, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 creation after initial OIDC login") t.Logf("Validating initial node creation at %s", time.Now().Format(TimestampFormat)) - var initialNodes []*v1.Node + var initialNodes []*apiv1.Node assert.EventuallyWithT(t, func(ct *assert.CollectT) { var err error @@ -1211,9 +1211,9 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { validateInitialConnection(t, headscale, expectedNodes) // Store initial node keys for comparison - initialMachineKey := initialNodes[0].GetMachineKey() - initialNodeKey := initialNodes[0].GetNodeKey() - initialNodeID := initialNodes[0].GetId() + initialMachineKey := initialNodes[0].GetMachineKey().Or("") + initialNodeKey := initialNodes[0].GetNodeKey().Or("") + initialNodeID := initialNodes[0].GetID().Or(0) // Logout user1 err = ts.Logout() @@ -1262,26 +1262,26 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { assert.NoError(ct, err, "Failed to list users during final validation") assert.Len(ct, listUsers, 1, "Should still have exactly 1 user after same-user relogin, got %d", len(listUsers)) - wantUsers := []*v1.User{ + wantUsers := []*apiv1.User{ { - Id: 1, - Name: "user1", - Email: "user1@headscale.net", - Provider: "oidc", - ProviderId: scenario.mockOIDC.Issuer() + "/user1", + ID: apiv1.NewOptUint64(1), + Name: apiv1.NewOptString("user1"), + Email: apiv1.NewOptString("user1@headscale.net"), + Provider: apiv1.NewOptString("oidc"), + ProviderId: apiv1.NewOptString(scenario.mockOIDC.Issuer() + "/user1"), }, } sort.Slice(listUsers, func(i, j int) bool { - return listUsers[i].GetId() < listUsers[j].GetId() + return listUsers[i].GetID().Or(0) < listUsers[j].GetID().Or(0) }) - if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" { + if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" { ct.Errorf("Final user validation failed - user1 should persist after same-user relogin: %s", diff) } }, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 persistence after same-user OIDC relogin cycle") - var finalNodes []*v1.Node + var finalNodes []*apiv1.Node t.Logf("Final node validation: checking node stability after same-user relogin at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -1293,19 +1293,19 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) { finalNode := finalNodes[0] // Machine key should be preserved (same physical machine) - assert.Equal(ct, initialMachineKey, finalNode.GetMachineKey(), "Machine key should be preserved for same user same node relogin") + assert.Equal(ct, initialMachineKey, finalNode.GetMachineKey().Or(""), "Machine key should be preserved for same user same node relogin") // Node ID should be preserved (same user, same machine) - assert.Equal(ct, initialNodeID, finalNode.GetId(), "Node ID should be preserved for same user same node relogin") + assert.Equal(ct, initialNodeID, finalNode.GetID().Or(0), "Node ID should be preserved for same user same node relogin") // Node key should be regenerated (new session after logout) - assert.NotEqual(ct, initialNodeKey, finalNode.GetNodeKey(), "Node key should be regenerated after logout/relogin even for same user") + assert.NotEqual(ct, initialNodeKey, finalNode.GetNodeKey().Or(""), "Node key should be regenerated after logout/relogin even for same user") t.Logf("Final validation complete - same user relogin key relationships verified at %s", time.Now().Format(TimestampFormat)) }, integrationutil.HAConvergeTimeout, 2*time.Second, "validating final node state after same-user OIDC relogin cycle with key preservation validation") // Security validation: user1's node should be active after relogin - activeUser1NodeID := types.NodeID(finalNodes[0].GetId()) + activeUser1NodeID := types.NodeID(finalNodes[0].GetID().Or(0)) t.Logf("Validating user1 node is online after same-user relogin at %s", time.Now().Format(TimestampFormat)) require.EventuallyWithT(t, func(c *assert.CollectT) { @@ -1389,10 +1389,10 @@ func TestOIDCExpiryAfterRestart(t *testing.T) { assert.Len(ct, nodes, 1) node := nodes[0] - assert.NotNil(ct, node.GetExpiry(), "Expiry should be set after OIDC login") + assert.True(ct, node.GetExpiry().IsSet(), "Expiry should be set after OIDC login") - if node.GetExpiry() != nil { - expiryTime := node.GetExpiry().AsTime() + if node.GetExpiry().IsSet() { + expiryTime := node.GetExpiry().Value assert.False(ct, expiryTime.IsZero(), "Expiry should not be zero time") initialExpiry = expiryTime @@ -1431,10 +1431,10 @@ func TestOIDCExpiryAfterRestart(t *testing.T) { assert.Len(ct, nodes, 1, "Should still have exactly 1 node after restart") node := nodes[0] - assert.NotNil(ct, node.GetExpiry(), "Expiry should NOT be nil after restart") + assert.True(ct, node.GetExpiry().IsSet(), "Expiry should NOT be nil after restart") - if node.GetExpiry() != nil { - expiryTime := node.GetExpiry().AsTime() + if node.GetExpiry().IsSet() { + expiryTime := node.GetExpiry().Value // This is the bug check - expiry should NOT be zero time assert.False(ct, expiryTime.IsZero(), @@ -1567,7 +1567,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) { assert.Len(ct, nodes, 1) gatewayNode := nodes[0] - gatewayNodeID = gatewayNode.GetId() + gatewayNodeID = gatewayNode.GetID().Or(0) assert.Len(ct, gatewayNode.GetAvailableRoutes(), 1) assert.Contains(ct, gatewayNode.GetAvailableRoutes(), advertiseRoute) }, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "route advertisement should propagate to headscale") @@ -1707,10 +1707,10 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) { assert.GreaterOrEqual(ct, len(users), 2, "Should have at least 2 users (gateway CLI user + oidcuser)") // Find gateway CLI user - var gatewayUser *v1.User + var gatewayUser *apiv1.User for _, user := range users { - if user.GetName() == "gateway" && user.GetProvider() == "" { + if user.GetName().Or("") == "gateway" && user.GetProvider().Or("") == "" { gatewayUser = user break } @@ -1719,14 +1719,14 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) { assert.NotNil(ct, gatewayUser, "Should have gateway CLI user") if gatewayUser != nil { - assert.Equal(ct, "gateway", gatewayUser.GetName()) + assert.Equal(ct, "gateway", gatewayUser.GetName().Or("")) } // Find OIDC user - var oidcUserFound *v1.User + var oidcUserFound *apiv1.User for _, user := range users { - if user.GetName() == "oidcuser" && user.GetProvider() == "oidc" { + if user.GetName().Or("") == "oidcuser" && user.GetProvider().Or("") == "oidc" { oidcUserFound = user break } @@ -1735,8 +1735,8 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) { assert.NotNil(ct, oidcUserFound, "Should have OIDC user") if oidcUserFound != nil { - assert.Equal(ct, "oidcuser", oidcUserFound.GetName()) - assert.Equal(ct, "oidcuser@headscale.net", oidcUserFound.GetEmail()) + assert.Equal(ct, "oidcuser", oidcUserFound.GetName().Or("")) + assert.Equal(ct, "oidcuser@headscale.net", oidcUserFound.GetEmail().Or("")) } }, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "headscale should have correct users and nodes") @@ -1834,7 +1834,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) { // Step 1: Verify initial route is advertised, approved, and SERVING t.Logf("Step 1: Verifying initial route is advertised, approved, and SERVING at %s", time.Now().Format(TimestampFormat)) - var initialNode *v1.Node + var initialNode *apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err := headscale.ListNodes() @@ -1856,7 +1856,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) { }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "initial route should be serving") require.NotNil(t, initialNode, "Initial node should be found") - initialNodeID := initialNode.GetId() + initialNodeID := initialNode.GetID().Or(0) t.Logf("Initial node ID: %d, Available: %v, Approved: %v, Serving: %v", initialNodeID, initialNode.GetAvailableRoutes(), initialNode.GetApprovedRoutes(), initialNode.GetSubnetRoutes()) @@ -1927,7 +1927,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) { "BUG #2896: Subnet routes should contain %s after relogin", advertiseRoute) // Also verify node ID was preserved (same node, not new registration) - assert.Equal(c, initialNodeID, node.GetId(), + assert.Equal(c, initialNodeID, node.GetID().Or(0), "Node ID should be preserved after same-user relogin") } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, diff --git a/integration/auth_web_flow_test.go b/integration/auth_web_flow_test.go index 752d78a3..d745188d 100644 --- a/integration/auth_web_flow_test.go +++ b/integration/auth_web_flow_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/integrationutil" @@ -98,7 +98,7 @@ func TestAuthWebFlowLogoutAndReloginSameUser(t *testing.T) { // Validate initial connection state validateInitialConnection(t, headscale, expectedNodes) - var listNodes []*v1.Node + var listNodes []*apiv1.Node t.Logf("Validating initial node count after web auth at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -256,7 +256,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) { // Validate initial connection state validateInitialConnection(t, headscale, expectedNodes) - var listNodes []*v1.Node + var listNodes []*apiv1.Node t.Logf("Validating initial node count after web auth at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -316,7 +316,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) { t.Logf("all clients logged back in as user1") - var user1Nodes []*v1.Node + var user1Nodes []*apiv1.Node t.Logf("Validating user1 node count after relogin at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -330,7 +330,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) { // Collect expected node IDs for user1 after relogin expectedUser1Nodes := make([]types.NodeID, 0, len(user1Nodes)) for _, node := range user1Nodes { - expectedUser1Nodes = append(expectedUser1Nodes, types.NodeID(node.GetId())) + expectedUser1Nodes = append(expectedUser1Nodes, types.NodeID(node.GetID().Or(0))) } // Validate connection state after relogin as user1 @@ -338,7 +338,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) { // Validate that user2's old nodes still exist in database (but are expired/offline) // When CLI registration creates new nodes for user1, user2's old nodes remain - var user2Nodes []*v1.Node + var user2Nodes []*apiv1.Node t.Logf("Validating user2 old nodes remain in database after CLI registration to user1 at %s", time.Now().Format(TimestampFormat)) assert.EventuallyWithT(t, func(ct *assert.CollectT) { diff --git a/integration/control.go b/integration/control.go index 256cc278..035245c9 100644 --- a/integration/control.go +++ b/integration/control.go @@ -3,7 +3,7 @@ package integration import ( "net/netip" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/hscontrol" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" @@ -24,19 +24,19 @@ type ControlServer interface { GetEndpoint() string WaitForRunning() error Restart() error - CreateUser(user string) (*v1.User, error) - CreateAuthKey(user uint64, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) - CreateAuthKeyWithTags(user uint64, reusable bool, ephemeral bool, tags []string) (*v1.PreAuthKey, error) - CreateAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*v1.PreAuthKey, error) + CreateUser(user string) (*apiv1.User, error) + CreateAuthKey(user uint64, reusable bool, ephemeral bool) (*apiv1.PreAuthKey, error) + CreateAuthKeyWithTags(user uint64, reusable bool, ephemeral bool, tags []string) (*apiv1.PreAuthKey, error) + CreateAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*apiv1.PreAuthKey, error) DeleteAuthKey(id uint64) error - ListNodes(users ...string) ([]*v1.Node, error) + ListNodes(users ...string) ([]*apiv1.Node, error) DeleteNode(nodeID uint64) error - NodesByUser() (map[string][]*v1.Node, error) - NodesByName() (map[string]*v1.Node, error) - ListUsers() ([]*v1.User, error) - MapUsers() (map[string]*v1.User, error) + NodesByUser() (map[string][]*apiv1.Node, error) + NodesByName() (map[string]*apiv1.Node, error) + ListUsers() ([]*apiv1.User, error) + MapUsers() (map[string]*apiv1.User, error) DeleteUser(userID uint64) error - ApproveRoutes(nodeID uint64, routes []netip.Prefix) (*v1.Node, error) + ApproveRoutes(nodeID uint64, routes []netip.Prefix) (*apiv1.Node, error) SetNodeTags(nodeID uint64, tags []string) error GetCert() []byte GetHostname() string diff --git a/integration/general_test.go b/integration/general_test.go index 27ce8197..e3120c25 100644 --- a/integration/general_test.go +++ b/integration/general_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/integrationutil" @@ -169,12 +169,12 @@ func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) { t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) } - key, err := scenario.CreatePreAuthKey(user.GetId(), true, true) + key, err := scenario.CreatePreAuthKey(user.GetID().Or(0), true, true) if err != nil { t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } - err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey().Or("")) if err != nil { t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } @@ -247,12 +247,12 @@ func TestEphemeral2006DeletedTooQuickly(t *testing.T) { t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) } - key, err := scenario.CreatePreAuthKey(user.GetId(), true, true) + key, err := scenario.CreatePreAuthKey(user.GetID().Or(0), true, true) if err != nil { t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err) } - err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) + err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey().Or("")) if err != nil { t.Fatalf("failed to run tailscale up for user %s: %s", userName, err) } @@ -386,7 +386,8 @@ func TestTaildrop(t *testing.T) { require.NoError(t, err) defer scenario.ShutdownAssertNoPanics(t) - err = scenario.CreateHeadscaleEnv([]tsic.Option{}, + err = scenario.CreateHeadscaleEnv( + []tsic.Option{}, hsic.WithTestName("taildrop"), ) requireNoErrHeadscaleEnv(t, err) @@ -402,7 +403,7 @@ func TestTaildrop(t *testing.T) { network := networks[0] // Create untagged nodes for user1 using all test versions - user1Key, err := scenario.CreatePreAuthKey(userMap["user1"].GetId(), true, false) + user1Key, err := scenario.CreatePreAuthKey(userMap["user1"].GetID().Or(0), true, false) require.NoError(t, err) var user1Clients []TailscaleClient @@ -414,7 +415,7 @@ func TestTaildrop(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), user1Key.GetKey()) + err = client.Login(headscale.GetEndpoint(), user1Key.GetKey().Or("")) require.NoError(t, err) err = client.WaitForRunning(integrationutil.PeerSyncTimeout()) @@ -425,7 +426,7 @@ func TestTaildrop(t *testing.T) { } // Create untagged nodes for user2 using all test versions - user2Key, err := scenario.CreatePreAuthKey(userMap["user2"].GetId(), true, false) + user2Key, err := scenario.CreatePreAuthKey(userMap["user2"].GetID().Or(0), true, false) require.NoError(t, err) var user2Clients []TailscaleClient @@ -437,7 +438,7 @@ func TestTaildrop(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), user2Key.GetKey()) + err = client.Login(headscale.GetEndpoint(), user2Key.GetKey().Or("")) require.NoError(t, err) err = client.WaitForRunning(integrationutil.PeerSyncTimeout()) @@ -449,7 +450,7 @@ func TestTaildrop(t *testing.T) { // Create a tagged device (tags-as-identity: tags come from PreAuthKey) // Use "head" version to test latest behavior - taggedKey, err := scenario.CreatePreAuthKeyWithTags(userMap["user1"].GetId(), true, false, []string{"tag:server"}) + taggedKey, err := scenario.CreatePreAuthKeyWithTags(userMap["user1"].GetID().Or(0), true, false, []string{"tag:server"}) require.NoError(t, err) taggedClient, err := scenario.CreateTailscaleNode( @@ -458,7 +459,7 @@ func TestTaildrop(t *testing.T) { ) require.NoError(t, err) - err = taggedClient.Login(headscale.GetEndpoint(), taggedKey.GetKey()) + err = taggedClient.Login(headscale.GetEndpoint(), taggedKey.GetKey().Or("")) require.NoError(t, err) err = taggedClient.WaitForRunning(integrationutil.PeerSyncTimeout()) @@ -767,8 +768,8 @@ func TestUpdateHostnameFromClient(t *testing.T) { // Pre-rewrite these were rejected by ApplyHostnameFromHostInfo with // "invalid characters" and the node was stuck on an invalid- // GivenName with the HostName update dropped. The assertions below - // verify both raw preservation ([v1.Node.Name]) and SaaS-matching sanitisation - // ([v1.Node.GivenName]) for each awkward input. + // verify both raw preservation ([apiv1.Node.Name]) and SaaS-matching sanitisation + // ([apiv1.Node.GivenName]) for each awkward input. hostnames := map[string]string{ "1": "Joe's Mac mini", "2": "Test@Host", @@ -814,7 +815,7 @@ func TestUpdateHostnameFromClient(t *testing.T) { // Wait for nodestore batch processing to complete // [state.NodeStore] batching timeout is 500ms, so we wait up to 1 second - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(ct *assert.CollectT) { err := executeAndUnmarshal( headscale, @@ -831,18 +832,18 @@ func TestUpdateHostnameFromClient(t *testing.T) { assert.Len(ct, nodes, 3, "Should have 3 nodes after hostname updates") for _, node := range nodes { - hostname := hostnames[strconv.FormatUint(node.GetId(), 10)] - assert.Equal(ct, hostname, node.GetName(), "Node name should match hostname") + hostname := hostnames[strconv.FormatUint(node.GetID().Or(0), 10)] + assert.Equal(ct, hostname, node.GetName().Or(""), "Node name should match hostname") // GivenName is sanitised via [dnsname.SanitizeHostname] (SaaS algorithm). - assert.Equal(ct, dnsname.SanitizeHostname(hostname), node.GetGivenName(), + assert.Equal(ct, dnsname.SanitizeHostname(hostname), node.GetGivenName().Or(""), "Given name should match SaaS hostname-sanitisation rules") } }, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second) // Rename givenName in nodes for _, node := range nodes { - givenName := fmt.Sprintf("%d-givenname", node.GetId()) + givenName := fmt.Sprintf("%d-givenname", node.GetID().Or(0)) _, err = headscale.Execute( []string{ "headscale", @@ -850,8 +851,9 @@ func TestUpdateHostnameFromClient(t *testing.T) { "rename", givenName, "--identifier", - strconv.FormatUint(node.GetId(), 10), - }) + strconv.FormatUint(node.GetID().Or(0), 10), + }, + ) require.NoError(t, err) } @@ -860,8 +862,8 @@ func TestUpdateHostnameFromClient(t *testing.T) { // Build a map of expected DNSNames by node ID expectedDNSNames := make(map[string]string) for _, node := range nodes { - nodeID := strconv.FormatUint(node.GetId(), 10) - expectedDNSNames[nodeID] = fmt.Sprintf("%d-givenname.headscale.net.", node.GetId()) + nodeID := strconv.FormatUint(node.GetID().Or(0), 10) + expectedDNSNames[nodeID] = fmt.Sprintf("%d-givenname.headscale.net.", node.GetID().Or(0)) } // Verify from each client's perspective @@ -931,9 +933,9 @@ func TestUpdateHostnameFromClient(t *testing.T) { } for _, node := range nodes { - hostname := hostnames[strconv.FormatUint(node.GetId(), 10)] - givenName := fmt.Sprintf("%d-givenname", node.GetId()) - if node.GetName() != hostname+"NEW" || node.GetGivenName() != givenName { + hostname := hostnames[strconv.FormatUint(node.GetID().Or(0), 10)] + givenName := fmt.Sprintf("%d-givenname", node.GetID().Or(0)) + if node.GetName().Or("") != hostname+"NEW" || node.GetGivenName().Or("") != givenName { return false } } @@ -993,15 +995,15 @@ func TestExpireNode(t *testing.T) { }) require.NoError(t, err) - var node v1.Node + var node apiv1.Node err = json.Unmarshal([]byte(result), &node) require.NoError(t, err) var expiredNodeKey key.NodePublic - err = expiredNodeKey.UnmarshalText([]byte(node.GetNodeKey())) + err = expiredNodeKey.UnmarshalText([]byte(node.GetNodeKey().Or(""))) require.NoError(t, err) - t.Logf("Node %s with node_key %s has been expired", node.GetName(), expiredNodeKey.String()) + t.Logf("Node %s with node_key %s has been expired", node.GetName().Or(""), expiredNodeKey.String()) // Verify that the expired node has been marked in all peers list. assert.EventuallyWithT(t, func(ct *assert.CollectT) { @@ -1009,7 +1011,7 @@ func TestExpireNode(t *testing.T) { status, err := client.Status() assert.NoError(ct, err) - if client.Hostname() != node.GetName() { + if client.Hostname() != node.GetName().Or("") { // Check if the expired node appears as expired in this client's peer list for key, peer := range status.Peer { if key == expiredNodeKey { @@ -1025,7 +1027,7 @@ func TestExpireNode(t *testing.T) { // Verify that the expired node has been marked in all peers list. for _, client := range allClients { - if client.Hostname() == node.GetName() { + if client.Hostname() == node.GetName().Or("") { continue } @@ -1060,11 +1062,11 @@ func TestExpireNode(t *testing.T) { peerStatus.Expired, ) - _, stderr, _ := client.Execute([]string{"tailscale", "ping", node.GetName()}) + _, stderr, _ := client.Execute([]string{"tailscale", "ping", node.GetName().Or("")}) if !strings.Contains(stderr, "node key has expired") { c.Errorf( "expected to be unable to ping expired host %q from %q", - node.GetName(), + node.GetName().Or(""), client.Hostname(), ) } @@ -1111,19 +1113,19 @@ func TestSetNodeExpiryInFuture(t *testing.T) { ) require.NoError(t, err) - var node v1.Node + var node apiv1.Node err = json.Unmarshal([]byte(result), &node) require.NoError(t, err) - require.True(t, node.GetExpiry().AsTime().After(time.Now())) - require.WithinDuration(t, targetExpiry, node.GetExpiry().AsTime(), 2*time.Second) + require.True(t, node.GetExpiry().Value.After(time.Now())) + require.WithinDuration(t, targetExpiry, node.GetExpiry().Value, 2*time.Second) var nodeKey key.NodePublic - err = nodeKey.UnmarshalText([]byte(node.GetNodeKey())) + err = nodeKey.UnmarshalText([]byte(node.GetNodeKey().Or(""))) require.NoError(t, err) for _, client := range allClients { - if client.Hostname() == node.GetName() { + if client.Hostname() == node.GetName().Or("") { continue } @@ -1208,10 +1210,10 @@ func TestDisableNodeExpiry(t *testing.T) { ) require.NoError(t, err) - var node v1.Node + var node apiv1.Node err = json.Unmarshal([]byte(result), &node) require.NoError(t, err) - require.NotNil(t, node.GetExpiry(), "node should have an expiry set") + require.True(t, node.GetExpiry().IsSet(), "node should have an expiry set") // Now disable the expiry. result, err = headscale.Execute( @@ -1224,23 +1226,23 @@ func TestDisableNodeExpiry(t *testing.T) { ) require.NoError(t, err) - var nodeDisabled v1.Node + var nodeDisabled apiv1.Node err = json.Unmarshal([]byte(result), &nodeDisabled) require.NoError(t, err) // Expiry should be nil (or zero time) when disabled. - if nodeDisabled.GetExpiry() != nil { - require.True(t, nodeDisabled.GetExpiry().AsTime().IsZero(), + if nodeDisabled.GetExpiry().IsSet() { + require.True(t, nodeDisabled.GetExpiry().Value.IsZero(), "node expiry should be zero/nil after disabling") } var nodeKey key.NodePublic - err = nodeKey.UnmarshalText([]byte(nodeDisabled.GetNodeKey())) + err = nodeKey.UnmarshalText([]byte(nodeDisabled.GetNodeKey().Or(""))) require.NoError(t, err) // Verify peers see the node as not expired. for _, client := range allClients { - if client.Hostname() == nodeDisabled.GetName() { + if client.Hostname() == nodeDisabled.GetName().Or("") { continue } @@ -1332,7 +1334,7 @@ func TestNodeOnlineStatus(t *testing.T) { return } - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(ct *assert.CollectT) { result, err := headscale.Execute([]string{ "headscale", "nodes", "list", "--output", "json", @@ -1347,9 +1349,9 @@ func TestNodeOnlineStatus(t *testing.T) { // All nodes should be online assert.Truef( ct, - node.GetOnline(), + node.GetOnline().Or(false), "expected %s to have online status in Headscale, marked as offline %s after start", - node.GetName(), + node.GetName().Or(""), time.Since(start), ) } @@ -1537,7 +1539,7 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) { require.NoError(t, err) // Test list all nodes after added otherUser - var nodeList []v1.Node + var nodeList []apiv1.Node err = executeAndUnmarshal( headscale, []string{ @@ -1551,8 +1553,8 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) { ) require.NoError(t, err) assert.Len(t, nodeList, 2) - assert.True(t, nodeList[0].GetOnline()) - assert.True(t, nodeList[1].GetOnline()) + assert.True(t, nodeList[0].GetOnline().Or(false)) + assert.True(t, nodeList[1].GetOnline().Or(false)) // Delete the first node, which is online _, err = headscale.Execute( @@ -1562,7 +1564,7 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) { "delete", "--identifier", // Delete the last added machine - fmt.Sprintf("%d", nodeList[0].GetId()), + fmt.Sprintf("%d", nodeList[0].GetID().Or(0)), "--output", "json", "--force", @@ -1571,7 +1573,7 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) { require.NoError(t, err) // Ensure that the node has been deleted, this did not occur due to a panic. - var nodeListAfter []v1.Node + var nodeListAfter []apiv1.Node assert.EventuallyWithT(t, func(ct *assert.CollectT) { err = executeAndUnmarshal( headscale, @@ -1601,6 +1603,6 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) { ) require.NoError(t, err) assert.Len(t, nodeListAfter, 1) - assert.True(t, nodeListAfter[0].GetOnline()) - assert.Equal(t, nodeList[1].GetId(), nodeListAfter[0].GetId()) + assert.True(t, nodeListAfter[0].GetOnline().Or(false)) + assert.Equal(t, nodeList[1].GetID().Or(0), nodeListAfter[0].GetID().Or(0)) } diff --git a/integration/grant_cap_test.go b/integration/grant_cap_test.go index 4c3bc9bf..d4a6b60a 100644 --- a/integration/grant_cap_test.go +++ b/integration/grant_cap_test.go @@ -151,7 +151,8 @@ func TestGrantCapRelay(t *testing.T) { require.NoError(t, err) // --- Create Relay R on usernet3, dual-homed to usernet1+usernet2 --- - relayR, err := scenario.CreateTailscaleNode("head", + relayR, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet3), ) require.NoError(t, err) @@ -159,10 +160,10 @@ func TestGrantCapRelay(t *testing.T) { defer func() { _, _, _ = relayR.Shutdown() }() pakRelay, err := scenario.CreatePreAuthKeyWithTags( - userMap["relay"].GetId(), false, false, []string{"tag:relay"}, + userMap["relay"].GetID().Or(0), false, false, []string{"tag:relay"}, ) require.NoError(t, err) - err = relayR.Login(headscale.GetEndpoint(), pakRelay.GetKey()) + err = relayR.Login(headscale.GetEndpoint(), pakRelay.GetKey().Or("")) require.NoError(t, err) err = relayR.WaitForRunning(30 * time.Second) require.NoError(t, err) @@ -184,7 +185,8 @@ func TestGrantCapRelay(t *testing.T) { require.NoError(t, err) // --- Create Client A on usernet1 only --- - clientA, err := scenario.CreateTailscaleNode("head", + clientA, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -192,16 +194,17 @@ func TestGrantCapRelay(t *testing.T) { defer func() { _, _, _ = clientA.Shutdown() }() pakClientA, err := scenario.CreatePreAuthKeyWithTags( - userMap["clienta"].GetId(), false, false, []string{"tag:client-a"}, + userMap["clienta"].GetID().Or(0), false, false, []string{"tag:client-a"}, ) require.NoError(t, err) - err = clientA.Login(headscale.GetEndpoint(), pakClientA.GetKey()) + err = clientA.Login(headscale.GetEndpoint(), pakClientA.GetKey().Or("")) require.NoError(t, err) err = clientA.WaitForRunning(30 * time.Second) require.NoError(t, err) // --- Create Client B on usernet2 only --- - clientB, err := scenario.CreateTailscaleNode("head", + clientB, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet2), ) require.NoError(t, err) @@ -209,10 +212,10 @@ func TestGrantCapRelay(t *testing.T) { defer func() { _, _, _ = clientB.Shutdown() }() pakClientB, err := scenario.CreatePreAuthKeyWithTags( - userMap["clientb"].GetId(), false, false, []string{"tag:client-b"}, + userMap["clientb"].GetID().Or(0), false, false, []string{"tag:client-b"}, ) require.NoError(t, err) - err = clientB.Login(headscale.GetEndpoint(), pakClientB.GetKey()) + err = clientB.Login(headscale.GetEndpoint(), pakClientB.GetKey().Or("")) require.NoError(t, err) err = clientB.WaitForRunning(30 * time.Second) require.NoError(t, err) @@ -629,7 +632,8 @@ func TestGrantCapDrive(t *testing.T) { require.NoError(t, err) // --- Create Sharer node --- - sharer, err := scenario.CreateTailscaleNode("head", + sharer, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -637,16 +641,17 @@ func TestGrantCapDrive(t *testing.T) { defer func() { _, _, _ = sharer.Shutdown() }() pakSharer, err := scenario.CreatePreAuthKeyWithTags( - userMap["sharer"].GetId(), false, false, []string{"tag:sharer"}, + userMap["sharer"].GetID().Or(0), false, false, []string{"tag:sharer"}, ) require.NoError(t, err) - err = sharer.Login(headscale.GetEndpoint(), pakSharer.GetKey()) + err = sharer.Login(headscale.GetEndpoint(), pakSharer.GetKey().Or("")) require.NoError(t, err) err = sharer.WaitForRunning(30 * time.Second) require.NoError(t, err) // --- Create RW client node --- - rwClient, err := scenario.CreateTailscaleNode("head", + rwClient, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -654,16 +659,17 @@ func TestGrantCapDrive(t *testing.T) { defer func() { _, _, _ = rwClient.Shutdown() }() pakRW, err := scenario.CreatePreAuthKeyWithTags( - userMap["rwclient"].GetId(), false, false, []string{"tag:rw-client"}, + userMap["rwclient"].GetID().Or(0), false, false, []string{"tag:rw-client"}, ) require.NoError(t, err) - err = rwClient.Login(headscale.GetEndpoint(), pakRW.GetKey()) + err = rwClient.Login(headscale.GetEndpoint(), pakRW.GetKey().Or("")) require.NoError(t, err) err = rwClient.WaitForRunning(30 * time.Second) require.NoError(t, err) // --- Create RO client node --- - roClient, err := scenario.CreateTailscaleNode("head", + roClient, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -671,16 +677,17 @@ func TestGrantCapDrive(t *testing.T) { defer func() { _, _, _ = roClient.Shutdown() }() pakRO, err := scenario.CreatePreAuthKeyWithTags( - userMap["roclient"].GetId(), false, false, []string{"tag:ro-client"}, + userMap["roclient"].GetID().Or(0), false, false, []string{"tag:ro-client"}, ) require.NoError(t, err) - err = roClient.Login(headscale.GetEndpoint(), pakRO.GetKey()) + err = roClient.Login(headscale.GetEndpoint(), pakRO.GetKey().Or("")) require.NoError(t, err) err = roClient.WaitForRunning(30 * time.Second) require.NoError(t, err) // --- Create No-access node --- - noAccess, err := scenario.CreateTailscaleNode("head", + noAccess, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -688,10 +695,10 @@ func TestGrantCapDrive(t *testing.T) { defer func() { _, _, _ = noAccess.Shutdown() }() pakNA, err := scenario.CreatePreAuthKeyWithTags( - userMap["noaccess"].GetId(), false, false, []string{"tag:no-access"}, + userMap["noaccess"].GetID().Or(0), false, false, []string{"tag:no-access"}, ) require.NoError(t, err) - err = noAccess.Login(headscale.GetEndpoint(), pakNA.GetKey()) + err = noAccess.Login(headscale.GetEndpoint(), pakNA.GetKey().Or("")) require.NoError(t, err) err = noAccess.WaitForRunning(30 * time.Second) require.NoError(t, err) diff --git a/integration/helpers.go b/integration/helpers.go index 7aa1ae1e..d206aad0 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -18,7 +18,7 @@ import ( "github.com/cenkalti/backoff/v5" "github.com/google/go-cmp/cmp" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" @@ -538,15 +538,15 @@ func requireAllClientsNetInfoAndDERP(t *testing.T, headscale ControlServer, expe // assertLastSeenSet validates that a node has a non-nil LastSeen timestamp. // Critical for ensuring node activity tracking is functioning properly. -func assertLastSeenSet(t *testing.T, node *v1.Node) { +func assertLastSeenSet(t *testing.T, node *apiv1.Node) { t.Helper() assert.NotNil(t, node) - assert.NotNil(t, node.GetLastSeen()) + assert.True(t, node.GetLastSeen().Set) } -func assertLastSeenSetWithCollect(c *assert.CollectT, node *v1.Node) { +func assertLastSeenSetWithCollect(c *assert.CollectT, node *apiv1.Node) { assert.NotNil(c, node) - assert.NotNil(c, node.GetLastSeen()) + assert.True(c, node.GetLastSeen().Set) } // assertCurlSuccessWithCollect asserts that a curl request succeeds with @@ -1071,14 +1071,14 @@ func oidcMockUser(username string, emailVerified bool) mockoidc.MockUser { // GetUserByName retrieves a user by name from the headscale server. // This is a common pattern used when creating preauth keys or managing users. -func GetUserByName(headscale ControlServer, username string) (*v1.User, error) { +func GetUserByName(headscale ControlServer, username string) (*apiv1.User, error) { users, err := headscale.ListUsers() if err != nil { return nil, fmt.Errorf("listing users: %w", err) } for _, u := range users { - if u.GetName() == username { + if u.GetName().Or("") == username { return u, nil } } @@ -1088,7 +1088,7 @@ func GetUserByName(headscale ControlServer, username string) (*v1.User, error) { // findNode returns the first node in nodes for which match returns true, // or nil if no node matches. -func findNode(nodes []*v1.Node, match func(*v1.Node) bool) *v1.Node { +func findNode(nodes []*apiv1.Node, match func(*apiv1.Node) bool) *apiv1.Node { for _, n := range nodes { if match(n) { return n @@ -1177,13 +1177,13 @@ func (s *Scenario) AddAndLoginClient( return nil, fmt.Errorf("getting user: %w", err) } - authKey, err := s.CreatePreAuthKey(user.GetId(), true, false) + authKey, err := s.CreatePreAuthKey(user.GetID().Or(0), true, false) if err != nil { return nil, fmt.Errorf("creating preauth key: %w", err) } // Login the new client - err = newClient.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = newClient.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { return nil, fmt.Errorf("logging in new client: %w", err) } diff --git a/integration/hsic/hsic.go b/integration/hsic/hsic.go index e20e6466..a5be567b 100644 --- a/integration/hsic/hsic.go +++ b/integration/hsic/hsic.go @@ -24,7 +24,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/hscontrol" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" @@ -1095,7 +1095,7 @@ func (t *HeadscaleInContainer) WaitForRunning() error { // CreateUser adds a new user to the Headscale instance. func (t *HeadscaleInContainer) CreateUser( user string, -) (*v1.User, error) { +) (*apiv1.User, error) { command := []string{ binHeadscale, "users", @@ -1115,7 +1115,7 @@ func (t *HeadscaleInContainer) CreateUser( return nil, err } - var u v1.User + var u apiv1.User err = json.Unmarshal([]byte(result), &u) if err != nil { @@ -1140,7 +1140,7 @@ type AuthKeyOptions struct { // CreateAuthKeyWithOptions creates a new "authorisation key" with the specified options. // This supports both user-owned and tags-only auth keys. -func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*v1.PreAuthKey, error) { +func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*apiv1.PreAuthKey, error) { command := []string{ binHeadscale, } @@ -1181,7 +1181,7 @@ func (t *HeadscaleInContainer) CreateAuthKeyWithOptions(opts AuthKeyOptions) (*v return nil, fmt.Errorf("executing create auth key command: %w", err) } - var preAuthKey v1.PreAuthKey + var preAuthKey apiv1.PreAuthKey err = json.Unmarshal([]byte(result), &preAuthKey) if err != nil { @@ -1197,7 +1197,7 @@ func (t *HeadscaleInContainer) CreateAuthKey( user uint64, reusable bool, ephemeral bool, -) (*v1.PreAuthKey, error) { +) (*apiv1.PreAuthKey, error) { return t.CreateAuthKeyWithOptions(AuthKeyOptions{ User: &user, Reusable: reusable, @@ -1212,7 +1212,7 @@ func (t *HeadscaleInContainer) CreateAuthKeyWithTags( reusable bool, ephemeral bool, tags []string, -) (*v1.PreAuthKey, error) { +) (*apiv1.PreAuthKey, error) { return t.CreateAuthKeyWithOptions(AuthKeyOptions{ User: &user, Reusable: reusable, @@ -1252,8 +1252,8 @@ func (t *HeadscaleInContainer) DeleteAuthKey( // specific users. func (t *HeadscaleInContainer) ListNodes( users ...string, -) ([]*v1.Node, error) { - var ret []*v1.Node +) ([]*apiv1.Node, error) { + var ret []*apiv1.Node execUnmarshal := func(command []string) error { result, _, err := dockertestutil.ExecuteCommand( @@ -1265,7 +1265,7 @@ func (t *HeadscaleInContainer) ListNodes( return fmt.Errorf("executing list node command: %w", err) } - var nodes []*v1.Node + var nodes []*apiv1.Node err = json.Unmarshal([]byte(result), &nodes) if err != nil { @@ -1293,8 +1293,8 @@ func (t *HeadscaleInContainer) ListNodes( } } - slices.SortFunc(ret, func(a, b *v1.Node) int { - return cmp.Compare(a.GetId(), b.GetId()) + slices.SortFunc(ret, func(a, b *apiv1.Node) int { + return cmp.Compare(a.GetID().Or(0), b.GetID().Or(0)) }) return ret, nil @@ -1324,37 +1324,38 @@ func (t *HeadscaleInContainer) DeleteNode(nodeID uint64) error { return nil } -func (t *HeadscaleInContainer) NodesByUser() (map[string][]*v1.Node, error) { +func (t *HeadscaleInContainer) NodesByUser() (map[string][]*apiv1.Node, error) { nodes, err := t.ListNodes() if err != nil { return nil, err } - userMap := make(map[string][]*v1.Node) + userMap := make(map[string][]*apiv1.Node) for _, node := range nodes { - name := node.GetUser().GetName() + user := node.GetUser().Value + name := user.GetName().Or("") userMap[name] = append(userMap[name], node) } return userMap, nil } -func (t *HeadscaleInContainer) NodesByName() (map[string]*v1.Node, error) { +func (t *HeadscaleInContainer) NodesByName() (map[string]*apiv1.Node, error) { nodes, err := t.ListNodes() if err != nil { return nil, err } - var nameMap map[string]*v1.Node + var nameMap map[string]*apiv1.Node for _, node := range nodes { - mak.Set(&nameMap, node.GetName(), node) + mak.Set(&nameMap, node.GetName().Or(""), node) } return nameMap, nil } // ListUsers returns a list of users from Headscale. -func (t *HeadscaleInContainer) ListUsers() ([]*v1.User, error) { +func (t *HeadscaleInContainer) ListUsers() ([]*apiv1.User, error) { command := []string{binHeadscale, "users", "list", flagOutput, "json"} result, _, err := dockertestutil.ExecuteCommand( @@ -1366,7 +1367,7 @@ func (t *HeadscaleInContainer) ListUsers() ([]*v1.User, error) { return nil, fmt.Errorf("executing list node command: %w", err) } - var users []*v1.User + var users []*apiv1.User err = json.Unmarshal([]byte(result), &users) if err != nil { @@ -1378,15 +1379,15 @@ func (t *HeadscaleInContainer) ListUsers() ([]*v1.User, error) { // MapUsers returns a map of users from Headscale. It is keyed by the // user name. -func (t *HeadscaleInContainer) MapUsers() (map[string]*v1.User, error) { +func (t *HeadscaleInContainer) MapUsers() (map[string]*apiv1.User, error) { users, err := t.ListUsers() if err != nil { return nil, err } - var userMap map[string]*v1.User + var userMap map[string]*apiv1.User for _, user := range users { - mak.Set(&userMap, user.GetName(), user) + mak.Set(&userMap, user.GetName().Or(""), user) } return userMap, nil @@ -1545,7 +1546,7 @@ func (h *HeadscaleInContainer) Restart() error { } // ApproveRoutes approves routes for a node. -func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) (*v1.Node, error) { +func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) (*apiv1.Node, error) { command := []string{ binHeadscale, "nodes", "approve-routes", flagOutput, "json", @@ -1567,7 +1568,7 @@ func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) ( ) } - var node *v1.Node + var node *apiv1.Node err = json.Unmarshal([]byte(result), &node) if err != nil { diff --git a/integration/route_test.go b/integration/route_test.go index beff6ff0..1bd09a0a 100644 --- a/integration/route_test.go +++ b/integration/route_test.go @@ -15,7 +15,7 @@ import ( cmpdiff "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" @@ -54,7 +54,8 @@ func TestEnablingRoutes(t *testing.T) { err = scenario.CreateHeadscaleEnv( []tsic.Option{tsic.WithAcceptRoutes()}, - hsic.WithTestName("rt-enable")) + hsic.WithTestName("rt-enable"), + ) requireNoErrHeadscaleEnv(t, err) allClients, err := scenario.ListTailscaleClients() @@ -87,7 +88,7 @@ func TestEnablingRoutes(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node // Wait for route advertisements to propagate to [state.NodeStore] assert.EventuallyWithT(t, func(ct *assert.CollectT) { var err error @@ -119,7 +120,7 @@ func TestEnablingRoutes(t *testing.T) { for _, node := range nodes { _, err := headscale.ApproveRoutes( - node.GetId(), + node.GetID().Or(0), util.MustStringsToPrefixes(node.GetAvailableRoutes()), ) require.NoError(t, err) @@ -181,11 +182,11 @@ func TestEnablingRoutes(t *testing.T) { assert.NoError(c, err) for _, node := range nodes { - if node.GetId() == 1 { + if node.GetID().Or(0) == 1 { assert.Len(c, node.GetAvailableRoutes(), 1) // 10.0.0.0/24 assert.Len(c, node.GetApprovedRoutes(), 1) // 10.0.1.0/24 assert.Empty(c, node.GetSubnetRoutes()) - } else if node.GetId() == 2 { + } else if node.GetID().Or(0) == 2 { assert.Len(c, node.GetAvailableRoutes(), 1) // 10.0.1.0/24 assert.Empty(c, node.GetApprovedRoutes()) assert.Empty(c, node.GetSubnetRoutes()) @@ -333,7 +334,7 @@ func TestHASubnetRouterFailover(t *testing.T) { requireNoErrSync(t, err) // Wait for route configuration changes after advertising routes - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -400,7 +401,7 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf(" Expected: Client can access webservice through router 1 only") _, err = headscale.ApproveRoutes( - MustFindNode(subRouter1.Hostname(), nodes).GetId(), + MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) @@ -450,7 +451,8 @@ func TestHASubnetRouterFailover(t *testing.T) { if srs1PeerStatus.PrimaryRoutes != nil { t.Logf("got list: %v, want in: %v", srs1PeerStatus.PrimaryRoutes.AsSlice(), pref) - assert.Contains(c, + assert.Contains( + c, srs1PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -479,11 +481,11 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 1 is primary validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, // Note: Router 2 and 3 are available but not approved }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)), }, }, "Router 1 should be primary for route "+pref.String()) @@ -498,7 +500,7 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf(" Expected: HA is now active - if router 1 fails, router 2 can take over") _, err = headscale.ApproveRoutes( - MustFindNode(subRouter2.Hostname(), nodes).GetId(), + MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) @@ -549,7 +551,8 @@ func TestHASubnetRouterFailover(t *testing.T) { if srs1PeerStatus.PrimaryRoutes != nil { t.Logf("got list: %v, want in: %v", srs1PeerStatus.PrimaryRoutes.AsSlice(), pref) - assert.Contains(c, + assert.Contains( + c, srs1PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -559,12 +562,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 1 still primary, router 2 approved but standby validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, // Note: Router 3 is available but not approved }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)), }, }, "Router 1 should remain primary after router 2 approval") @@ -594,12 +597,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 1 primary, router 2 approved (standby) validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, // Note: Router 3 is available but not approved }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)), }, }, "Router 1 primary with router 2 as standby") @@ -615,7 +618,7 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf(" Expected: Full HA configuration with 1 PRIMARY + 2 STANDBY routers") _, err = headscale.ApproveRoutes( - MustFindNode(subRouter3.Hostname(), nodes).GetId(), + MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) @@ -664,7 +667,8 @@ func TestHASubnetRouterFailover(t *testing.T) { if srs1PeerStatus.PrimaryRoutes != nil { t.Logf("got list: %v, want in: %v", srs1PeerStatus.PrimaryRoutes.AsSlice(), pref) - assert.Contains(c, + assert.Contains( + c, srs1PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -702,12 +706,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - all 3 routers approved, router 1 still primary validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)), }, }, "Router 1 primary with all 3 routers approved") @@ -755,7 +759,8 @@ func TestHASubnetRouterFailover(t *testing.T) { requirePeerSubnetRoutesWithCollect(c, srs3PeerStatus, nil) if srs2PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs2PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -782,11 +787,11 @@ func TestHASubnetRouterFailover(t *testing.T) { validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ // Router 1 is disconnected, so not in AvailableRoutes - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)), }, }, "Router 2 should be primary after router 1 failure") @@ -854,10 +859,10 @@ func TestHASubnetRouterFailover(t *testing.T) { validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ // Routers 1 and 2 are disconnected, so not in AvailableRoutes - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)), }, }, "Router 3 should be primary after router 2 failure") @@ -905,7 +910,8 @@ func TestHASubnetRouterFailover(t *testing.T) { requirePeerSubnetRoutesWithCollect(c, srs3PeerStatus, []netip.Prefix{pref}) if srs3PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs3PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -931,12 +937,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 3 remains primary after router 1 comes back validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, // Router 2 is still disconnected - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)), }, }, "Router 3 should remain primary after router 1 recovery") @@ -986,7 +992,8 @@ func TestHASubnetRouterFailover(t *testing.T) { requirePeerSubnetRoutesWithCollect(c, srs3PeerStatus, []netip.Prefix{pref}) if srs3PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs3PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -1012,12 +1019,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 3 remains primary after all routers back online validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)), }, }, "Router 3 should remain primary after full recovery") @@ -1030,7 +1037,7 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf(" Expected: Router 1 (%s) should become new PRIMARY (lowest ID with approved route)", subRouter1.Hostname()) t.Logf(" Expected: Router 2 (%s) remains STANDBY", subRouter2.Hostname()) t.Logf(" Expected: Router 3 (%s) goes to advertised-only state (no longer serving)", subRouter3.Hostname()) - _, err = headscale.ApproveRoutes(MustFindNode(subRouter3.Hostname(), nodes).GetId(), []netip.Prefix{}) + _, err = headscale.ApproveRoutes(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0), []netip.Prefix{}) // Wait for nodestore batch processing and route state changes to complete // [state.NodeStore] batching timeout is 500ms, so we wait up to 10 seconds for route failover @@ -1071,7 +1078,8 @@ func TestHASubnetRouterFailover(t *testing.T) { requirePeerSubnetRoutesWithCollect(c, srs3PeerStatus, nil) if srs1PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs1PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -1097,12 +1105,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state - router 1 is primary after router 3 route disabled validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, // Router 3's route is no longer approved, so not in AvailableRoutes }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)), }, }, "Router 1 should be primary after router 3 route disabled") @@ -1116,7 +1124,7 @@ func TestHASubnetRouterFailover(t *testing.T) { t.Logf(" Expected: Router 2 (%s) should become new PRIMARY (only remaining approved route)", subRouter2.Hostname()) t.Logf(" Expected: Router 1 (%s) goes to advertised-only state", subRouter1.Hostname()) t.Logf(" Expected: Router 3 (%s) remains advertised-only", subRouter3.Hostname()) - _, err = headscale.ApproveRoutes(MustFindNode(subRouter1.Hostname(), nodes).GetId(), []netip.Prefix{}) + _, err = headscale.ApproveRoutes(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{}) // Wait for nodestore batch processing and route state changes to complete // [state.NodeStore] batching timeout is 500ms, so we wait up to 10 seconds for route failover @@ -1157,7 +1165,8 @@ func TestHASubnetRouterFailover(t *testing.T) { requirePeerSubnetRoutesWithCollect(c, srs3PeerStatus, nil) if srs2PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs2PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -1184,11 +1193,11 @@ func TestHASubnetRouterFailover(t *testing.T) { validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ // Router 1's route is no longer approved, so not in AvailableRoutes - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, // Router 3's route is still not approved }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)), }, }, "Router 2 should be primary after router 1 route disabled") @@ -1205,7 +1214,7 @@ func TestHASubnetRouterFailover(t *testing.T) { r1Node := MustFindNode(subRouter1.Hostname(), nodes) _, err = headscale.ApproveRoutes( - r1Node.GetId(), + r1Node.GetID().Or(0), util.MustStringsToPrefixes(r1Node.GetAvailableRoutes()), ) @@ -1242,7 +1251,8 @@ func TestHASubnetRouterFailover(t *testing.T) { assert.Nil(c, srs3PeerStatus.PrimaryRoutes) if srs2PeerStatus.PrimaryRoutes != nil { - assert.Contains(c, + assert.Contains( + c, srs2PeerStatus.PrimaryRoutes.AsSlice(), pref, ) @@ -1268,12 +1278,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state after router 1 re-approval validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, // Router 3 route is still not approved }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)), }, }, "Router 2 should remain primary after router 1 re-approval") @@ -1290,7 +1300,7 @@ func TestHASubnetRouterFailover(t *testing.T) { r3Node := MustFindNode(subRouter3.Hostname(), nodes) _, err = headscale.ApproveRoutes( - r3Node.GetId(), + r3Node.GetID().Or(0), util.MustStringsToPrefixes(r3Node.GetAvailableRoutes()), ) @@ -1310,12 +1320,12 @@ func TestHASubnetRouterFailover(t *testing.T) { // Validate primary routes table state after router 3 re-approval validatePrimaryRoutes(t, headscale, &types.DebugRoutes{ AvailableRoutes: map[types.NodeID][]netip.Prefix{ - types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref}, - types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref}, + types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)): {pref}, + types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetID().Or(0)): {pref}, }, PrimaryRoutes: map[string]types.NodeID{ - pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()), + pref.String(): types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)), }, }, "Router 2 should remain primary after router 3 re-approval") @@ -1426,7 +1436,7 @@ func TestSubnetRouteACL(t *testing.T) { requireNoErrSync(t, err) // Wait for route advertisements to propagate to the server - var nodes []*v1.Node + var nodes []*apiv1.Node require.EventuallyWithT(t, func(c *assert.CollectT) { var err error @@ -1437,12 +1447,12 @@ func TestSubnetRouteACL(t *testing.T) { // Find the node that should have the route by checking node IDs var ( - routeNode *v1.Node - otherNode *v1.Node + routeNode *apiv1.Node + otherNode *apiv1.Node ) for _, node := range nodes { - nodeIDStr := strconv.FormatUint(node.GetId(), 10) + nodeIDStr := strconv.FormatUint(node.GetID().Or(0), 10) if _, shouldHaveRoute := expectedRoutes[nodeIDStr]; shouldHaveRoute { routeNode = node } else { @@ -1623,7 +1633,7 @@ func TestEnablingExitRoutes(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { var err error @@ -1654,12 +1664,12 @@ func TestEnablingExitRoutes(t *testing.T) { // Enable all routes, but do v4 on one and v6 on other to ensure they // are both added since they are exit routes. _, err = headscale.ApproveRoutes( - nodes[0].GetId(), + nodes[0].GetID().Or(0), []netip.Prefix{tsaddr.AllIPv4()}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - nodes[1].GetId(), + nodes[1].GetID().Or(0), []netip.Prefix{tsaddr.AllIPv6()}, ) require.NoError(t, err) @@ -1754,7 +1764,7 @@ func TestExitRoutesWithAutogroupInternetACL(t *testing.T) { // so the standard WaitForTailscaleSync wait would deadlock here — // the post-approval [assert.EventuallyWithT] block below covers the peer // state we actually care about. - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -1770,12 +1780,12 @@ func TestExitRoutesWithAutogroupInternetACL(t *testing.T) { // alice's exit. The bug fix is about visibility, not which node // is chosen. _, err = headscale.ApproveRoutes( - nodes[0].GetId(), + nodes[0].GetID().Or(0), []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - nodes[1].GetId(), + nodes[1].GetID().Or(0), []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()}, ) require.NoError(t, err) @@ -1859,7 +1869,8 @@ func TestSubnetRouterMultiNetwork(t *testing.T) { require.NoErrorf(t, err, "failed to create scenario: %s", err) defer scenario.ShutdownAssertNoPanics(t) - err = scenario.CreateHeadscaleEnv([]tsic.Option{tsic.WithAcceptRoutes()}, + err = scenario.CreateHeadscaleEnv( + []tsic.Option{tsic.WithAcceptRoutes()}, hsic.WithTestName("rt-multinet"), ) requireNoErrHeadscaleEnv(t, err) @@ -1902,7 +1913,7 @@ func TestSubnetRouterMultiNetwork(t *testing.T) { _, _, err = user1c.Execute(command) require.NoErrorf(t, err, "failed to advertise route: %s", err) - var nodes []*v1.Node + var nodes []*apiv1.Node // Wait for route advertisements to propagate to [state.NodeStore] assert.EventuallyWithT(t, func(ct *assert.CollectT) { var err error @@ -1929,7 +1940,7 @@ func TestSubnetRouterMultiNetwork(t *testing.T) { // Enable route _, err = headscale.ApproveRoutes( - nodes[0].GetId(), + nodes[0].GetID().Or(0), []netip.Prefix{*pref}, ) require.NoError(t, err) @@ -2010,7 +2021,8 @@ func TestSubnetRouterMultiNetworkExitNode(t *testing.T) { require.NoErrorf(t, err, "failed to create scenario: %s", err) defer scenario.ShutdownAssertNoPanics(t) - err = scenario.CreateHeadscaleEnv([]tsic.Option{}, + err = scenario.CreateHeadscaleEnv( + []tsic.Option{}, hsic.WithTestName("rt-multinetexit"), ) requireNoErrHeadscaleEnv(t, err) @@ -2058,7 +2070,7 @@ func TestSubnetRouterMultiNetworkExitNode(t *testing.T) { _, _, err = user1c.Execute(command) require.NoErrorf(t, err, "failed to advertise routes: %s", err) - var nodes []*v1.Node + var nodes []*apiv1.Node // Wait for route advertisements to propagate (3 routes: v4 exit + v6 exit + subnet). assert.EventuallyWithT(t, func(ct *assert.CollectT) { var err error @@ -2084,7 +2096,7 @@ func TestSubnetRouterMultiNetworkExitNode(t *testing.T) { }, integrationutil.ScaledTimeout(5*time.Second), integrationutil.FastPoll, "Verifying no routes sent to client before approval") // Approve exit routes and subnet route. - _, err = headscale.ApproveRoutes(nodes[0].GetId(), []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6(), *route}) + _, err = headscale.ApproveRoutes(nodes[0].GetID().Or(0), []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6(), *route}) require.NoError(t, err) // Wait for route state changes to propagate. @@ -2151,9 +2163,9 @@ func TestSubnetRouterMultiNetworkExitNode(t *testing.T) { }, 10*time.Second, 200*time.Millisecond, "user2 traceroute should go through user1 exit node") } -func MustFindNode(hostname string, nodes []*v1.Node) *v1.Node { +func MustFindNode(hostname string, nodes []*apiv1.Node) *apiv1.Node { for _, node := range nodes { - if node.GetName() == hostname { + if node.GetName().Or("") == hostname { return node } } @@ -2447,7 +2459,7 @@ func TestAutoApproveMultiNetwork(t *testing.T) { require.NoErrorf(t, err, "failed to create scenario: %s", err) defer scenario.ShutdownAssertNoPanics(t) - var nodes []*v1.Node + var nodes []*apiv1.Node opts := []hsic.Option{ hsic.WithTestName("autoapprovemulti"), @@ -2477,7 +2489,8 @@ func TestAutoApproveMultiNetwork(t *testing.T) { } } - err = scenario.createHeadscaleEnvWithTags(tt.withURL, tsOpts, preAuthKeyTags, webauthTagUser, + err = scenario.createHeadscaleEnvWithTags( + tt.withURL, tsOpts, preAuthKeyTags, webauthTagUser, opts..., ) requireNoErrHeadscaleEnv(t, err) @@ -2518,7 +2531,8 @@ func TestAutoApproveMultiNetwork(t *testing.T) { require.NoError(t, err) if advertiseDuringUp { - tsOpts = append(tsOpts, + tsOpts = append( + tsOpts, tsic.WithExtraLoginArgs([]string{"--advertise-routes=" + route.String()}), ) } @@ -2565,16 +2579,16 @@ func TestAutoApproveMultiNetwork(t *testing.T) { // If the approver is a tag, create a tagged PreAuthKey // (tags-as-identity model: tags come from PreAuthKey, not --advertise-tags) - var pak *v1.PreAuthKey + var pak *apiv1.PreAuthKey if strings.HasPrefix(tt.approver, "tag:") { - pak, err = scenario.CreatePreAuthKeyWithTags(userMap["user1"].GetId(), false, false, []string{tt.approver}) + pak, err = scenario.CreatePreAuthKeyWithTags(userMap["user1"].GetID().Or(0), false, false, []string{tt.approver}) } else { - pak, err = scenario.CreatePreAuthKey(userMap["user1"].GetId(), false, false) + pak, err = scenario.CreatePreAuthKey(userMap["user1"].GetID().Or(0), false, false) } require.NoError(t, err) - err = routerUsernet1.Login(headscale.GetEndpoint(), pak.GetKey()) + err = routerUsernet1.Login(headscale.GetEndpoint(), pak.GetKey().Or("")) require.NoError(t, err) } // extra creation end. @@ -2646,7 +2660,7 @@ func TestAutoApproveMultiNetwork(t *testing.T) { routerNode := MustFindNode(routerUsernet1.Hostname(), nodes) t.Logf("Initial auto-approval check - Router node %s: announced=%v, approved=%v, subnet=%v", - routerNode.GetName(), + routerNode.GetName().Or(""), routerNode.GetAvailableRoutes(), routerNode.GetApprovedRoutes(), routerNode.GetSubnetRoutes()) @@ -2740,7 +2754,7 @@ func TestAutoApproveMultiNetwork(t *testing.T) { routerNode := MustFindNode(routerUsernet1.Hostname(), nodes) t.Logf("After policy removal - Router node %s: announced=%v, approved=%v, subnet=%v", - routerNode.GetName(), + routerNode.GetName().Or(""), routerNode.GetAvailableRoutes(), routerNode.GetApprovedRoutes(), routerNode.GetSubnetRoutes()) @@ -2791,7 +2805,7 @@ func TestAutoApproveMultiNetwork(t *testing.T) { // Disable the route, making it unavailable since it is no longer auto-approved _, err = headscale.ApproveRoutes( - MustFindNode(routerUsernet1.Hostname(), nodes).GetId(), + MustFindNode(routerUsernet1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{}, ) require.NoError(t, err) @@ -3078,10 +3092,10 @@ func requirePeerSubnetRoutesWithCollect(c *assert.CollectT, status *ipnstate.Pee } } -func requireNodeRouteCountWithCollect(c *assert.CollectT, node *v1.Node, announced, approved, subnet int) { - assert.Lenf(c, node.GetAvailableRoutes(), announced, "expected %q announced routes(%v) to have %d route, had %d", node.GetName(), node.GetAvailableRoutes(), announced, len(node.GetAvailableRoutes())) - assert.Lenf(c, node.GetApprovedRoutes(), approved, "expected %q approved routes(%v) to have %d route, had %d", node.GetName(), node.GetApprovedRoutes(), approved, len(node.GetApprovedRoutes())) - assert.Lenf(c, node.GetSubnetRoutes(), subnet, "expected %q subnet routes(%v) to have %d route, had %d", node.GetName(), node.GetSubnetRoutes(), subnet, len(node.GetSubnetRoutes())) +func requireNodeRouteCountWithCollect(c *assert.CollectT, node *apiv1.Node, announced, approved, subnet int) { + assert.Lenf(c, node.GetAvailableRoutes(), announced, "expected %q announced routes(%v) to have %d route, had %d", node.GetName().Or(""), node.GetAvailableRoutes(), announced, len(node.GetAvailableRoutes())) + assert.Lenf(c, node.GetApprovedRoutes(), approved, "expected %q approved routes(%v) to have %d route, had %d", node.GetName().Or(""), node.GetApprovedRoutes(), approved, len(node.GetApprovedRoutes())) + assert.Lenf(c, node.GetSubnetRoutes(), subnet, "expected %q subnet routes(%v) to have %d route, had %d", node.GetName().Or(""), node.GetSubnetRoutes(), subnet, len(node.GetSubnetRoutes())) } // TestSubnetRouteACLFiltering tests that a node can only access subnet routes @@ -3159,9 +3173,10 @@ func TestSubnetRouteACLFiltering(t *testing.T) { err = json.Unmarshal([]byte(aclPolicyStr), aclPolicy) require.NoError(t, err) - err = scenario.CreateHeadscaleEnv([]tsic.Option{ - tsic.WithAcceptRoutes(), - }, hsic.WithTestName("routeaclfilter"), + err = scenario.CreateHeadscaleEnv( + []tsic.Option{ + tsic.WithAcceptRoutes(), + }, hsic.WithTestName("routeaclfilter"), hsic.WithACLPolicy(aclPolicy), hsic.WithPolicyMode(types.PolicyModeDB), // test updates policy at runtime via CLI ) @@ -3218,7 +3233,7 @@ func TestSubnetRouteACLFiltering(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var routerNode, nodeNode *v1.Node + var routerNode, nodeNode *apiv1.Node // Wait for route advertisements to propagate to [state.NodeStore] assert.EventuallyWithT(t, func(ct *assert.CollectT) { // List nodes and verify the router has 3 available routes @@ -3240,7 +3255,7 @@ func TestSubnetRouteACLFiltering(t *testing.T) { // Approve all routes for the router _, err = headscale.ApproveRoutes( - routerNode.GetId(), + routerNode.GetID().Or(0), util.MustStringsToPrefixes(routerNode.GetAvailableRoutes()), ) require.NoError(t, err) @@ -3401,7 +3416,8 @@ func TestGrantViaSubnetSteering(t *testing.T) { // co-router visibility the HA primary's subnet appears in the // co-router's AllowedIPs, and --accept-routes would install a // system route that conflicts with local subnet forwarding. - routerA, err := scenario.CreateTailscaleNode("head", + routerA, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -3409,16 +3425,17 @@ func TestGrantViaSubnetSteering(t *testing.T) { defer func() { _, _, _ = routerA.Shutdown() }() pakRouterA, err := scenario.CreatePreAuthKeyWithTags( - userMap["router"].GetId(), false, false, []string{"tag:router-a"}, + userMap["router"].GetID().Or(0), false, false, []string{"tag:router-a"}, ) require.NoError(t, err) - err = routerA.Login(headscale.GetEndpoint(), pakRouterA.GetKey()) + err = routerA.Login(headscale.GetEndpoint(), pakRouterA.GetKey().Or("")) require.NoError(t, err) err = routerA.WaitForRunning(30 * time.Second) require.NoError(t, err) // Create Router B (tag:router-b) on usernet1. - routerB, err := scenario.CreateTailscaleNode("head", + routerB, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet1), ) require.NoError(t, err) @@ -3426,16 +3443,17 @@ func TestGrantViaSubnetSteering(t *testing.T) { defer func() { _, _, _ = routerB.Shutdown() }() pakRouterB, err := scenario.CreatePreAuthKeyWithTags( - userMap["router"].GetId(), false, false, []string{"tag:router-b"}, + userMap["router"].GetID().Or(0), false, false, []string{"tag:router-b"}, ) require.NoError(t, err) - err = routerB.Login(headscale.GetEndpoint(), pakRouterB.GetKey()) + err = routerB.Login(headscale.GetEndpoint(), pakRouterB.GetKey().Or("")) require.NoError(t, err) err = routerB.WaitForRunning(30 * time.Second) require.NoError(t, err) // Create Client A (tag:group-a) on usernet2. - clientA, err := scenario.CreateTailscaleNode("head", + clientA, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet2), tsic.WithAcceptRoutes(), ) @@ -3444,16 +3462,17 @@ func TestGrantViaSubnetSteering(t *testing.T) { defer func() { _, _, _ = clientA.Shutdown() }() pakClientA, err := scenario.CreatePreAuthKeyWithTags( - userMap["client"].GetId(), false, false, []string{"tag:group-a"}, + userMap["client"].GetID().Or(0), false, false, []string{"tag:group-a"}, ) require.NoError(t, err) - err = clientA.Login(headscale.GetEndpoint(), pakClientA.GetKey()) + err = clientA.Login(headscale.GetEndpoint(), pakClientA.GetKey().Or("")) require.NoError(t, err) err = clientA.WaitForRunning(30 * time.Second) require.NoError(t, err) // Create Client B (tag:group-b) on usernet2. - clientB, err := scenario.CreateTailscaleNode("head", + clientB, err := scenario.CreateTailscaleNode( + "head", tsic.WithNetwork(usernet2), tsic.WithAcceptRoutes(), ) @@ -3462,10 +3481,10 @@ func TestGrantViaSubnetSteering(t *testing.T) { defer func() { _, _, _ = clientB.Shutdown() }() pakClientB, err := scenario.CreatePreAuthKeyWithTags( - userMap["client"].GetId(), false, false, []string{"tag:group-b"}, + userMap["client"].GetID().Or(0), false, false, []string{"tag:group-b"}, ) require.NoError(t, err) - err = clientB.Login(headscale.GetEndpoint(), pakClientB.GetKey()) + err = clientB.Login(headscale.GetEndpoint(), pakClientB.GetKey().Or("")) require.NoError(t, err) err = clientB.WaitForRunning(30 * time.Second) require.NoError(t, err) @@ -3497,7 +3516,7 @@ func TestGrantViaSubnetSteering(t *testing.T) { routerANode := MustFindNode(routerA.Hostname(), nodes) t.Logf("Router A %s: announced=%v, approved=%v, subnet=%v", - routerANode.GetName(), + routerANode.GetName().Or(""), routerANode.GetAvailableRoutes(), routerANode.GetApprovedRoutes(), routerANode.GetSubnetRoutes()) @@ -3506,7 +3525,7 @@ func TestGrantViaSubnetSteering(t *testing.T) { routerBNode := MustFindNode(routerB.Hostname(), nodes) t.Logf("Router B %s: announced=%v, approved=%v, subnet=%v", - routerBNode.GetName(), + routerBNode.GetName().Or(""), routerBNode.GetAvailableRoutes(), routerBNode.GetApprovedRoutes(), routerBNode.GetSubnetRoutes()) @@ -3697,7 +3716,7 @@ func TestHASubnetRouterPingFailover(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -3707,19 +3726,19 @@ func TestHASubnetRouterPingFailover(t *testing.T) { // Approve routes on both routers. _, err = headscale.ApproveRoutes( - MustFindNode(subRouter1.Hostname(), nodes).GetId(), + MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - MustFindNode(subRouter2.Hostname(), nodes).GetId(), + MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) - nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()) - nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()) + nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)) + nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)) // Wait for HA to be set up: router 1 primary, router 2 standby. assert.EventuallyWithT(t, func(c *assert.CollectT) { @@ -3937,7 +3956,7 @@ func TestHASubnetRouterFailoverBothOffline(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -3947,19 +3966,19 @@ func TestHASubnetRouterFailoverBothOffline(t *testing.T) { // Approve the route on both routers explicitly. _, err = headscale.ApproveRoutes( - MustFindNode(subRouter1.Hostname(), nodes).GetId(), + MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - MustFindNode(subRouter2.Hostname(), nodes).GetId(), + MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) - nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()) - nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()) + nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)) + nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)) // Sanity: r1 starts as primary (lower NodeID). assert.EventuallyWithT(t, func(c *assert.CollectT) { @@ -4142,7 +4161,7 @@ func TestHASubnetRouterFailoverBothOfflineCablePull(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -4151,18 +4170,18 @@ func TestHASubnetRouterFailoverBothOfflineCablePull(t *testing.T) { }, propagationTime, 200*time.Millisecond, "nodes registered") _, err = headscale.ApproveRoutes( - MustFindNode(subRouter1.Hostname(), nodes).GetId(), + MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - MustFindNode(subRouter2.Hostname(), nodes).GetId(), + MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) - nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()) + nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)) // Sanity: r1 starts as primary. assert.EventuallyWithT(t, func(c *assert.CollectT) { @@ -4366,7 +4385,7 @@ func TestHASubnetRouterFailoverDockerDisconnect(t *testing.T) { err = scenario.WaitForTailscaleSync() requireNoErrSync(t, err) - var nodes []*v1.Node + var nodes []*apiv1.Node assert.EventuallyWithT(t, func(c *assert.CollectT) { nodes, err = headscale.ListNodes() @@ -4375,19 +4394,19 @@ func TestHASubnetRouterFailoverDockerDisconnect(t *testing.T) { }, propagationTime, 200*time.Millisecond, "nodes registered") _, err = headscale.ApproveRoutes( - MustFindNode(subRouter1.Hostname(), nodes).GetId(), + MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) _, err = headscale.ApproveRoutes( - MustFindNode(subRouter2.Hostname(), nodes).GetId(), + MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0), []netip.Prefix{pref}, ) require.NoError(t, err) - nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()) - nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()) + nodeID1 := types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetID().Or(0)) + nodeID2 := types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetID().Or(0)) // requirePrimary blocks until headscale reports want as the // primary advertiser for pref. diff --git a/integration/scenario.go b/integration/scenario.go index ab31546d..271a863e 100644 --- a/integration/scenario.go +++ b/integration/scenario.go @@ -21,7 +21,7 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" "github.com/juanfont/headscale/hscontrol/capver" "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/integration/dockertestutil" @@ -520,7 +520,7 @@ func (s *Scenario) CreatePreAuthKey( user uint64, reusable bool, ephemeral bool, -) (*v1.PreAuthKey, error) { +) (*apiv1.PreAuthKey, error) { if headscale, err := s.Headscale(); err == nil { //nolint:noinlineerr key, err := headscale.CreateAuthKey(user, reusable, ephemeral) if err != nil { @@ -535,7 +535,7 @@ func (s *Scenario) CreatePreAuthKey( // CreatePreAuthKeyWithOptions creates a "pre authorised key" with the specified options // to be created in the Headscale instance on behalf of the [Scenario]. -func (s *Scenario) CreatePreAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*v1.PreAuthKey, error) { +func (s *Scenario) CreatePreAuthKeyWithOptions(opts hsic.AuthKeyOptions) (*apiv1.PreAuthKey, error) { headscale, err := s.Headscale() if err != nil { return nil, fmt.Errorf("creating preauth key with options: %w", errNoHeadscaleAvailable) @@ -556,7 +556,7 @@ func (s *Scenario) CreatePreAuthKeyWithTags( reusable bool, ephemeral bool, tags []string, -) (*v1.PreAuthKey, error) { +) (*apiv1.PreAuthKey, error) { headscale, err := s.Headscale() if err != nil { return nil, fmt.Errorf("creating preauth key with tags: %w", errNoHeadscaleAvailable) @@ -572,7 +572,7 @@ func (s *Scenario) CreatePreAuthKeyWithTags( // CreateUser creates a [User] to be created in the // Headscale instance on behalf of the [Scenario]. -func (s *Scenario) CreateUser(user string) (*v1.User, error) { +func (s *Scenario) CreateUser(user string) (*apiv1.User, error) { if headscale, err := s.Headscale(); err == nil { //nolint:noinlineerr u, err := headscale.CreateUser(user) if err != nil { @@ -925,7 +925,7 @@ func (s *Scenario) createHeadscaleEnvWithTags( } for _, user := range s.spec.Users { - var u *v1.User + var u *apiv1.User if s.spec.OIDCSkipUserCreation { // Only register locally — OIDC login will create the headscale user. @@ -964,18 +964,18 @@ func (s *Scenario) createHeadscaleEnvWithTags( } } else { // Use tagged PreAuthKey if tags are provided (tags-as-identity model) - var key *v1.PreAuthKey + var key *apiv1.PreAuthKey if len(preAuthKeyTags) > 0 { - key, err = s.CreatePreAuthKeyWithTags(u.GetId(), true, false, preAuthKeyTags) + key, err = s.CreatePreAuthKeyWithTags(u.GetID().Or(0), true, false, preAuthKeyTags) } else { - key, err = s.CreatePreAuthKey(u.GetId(), true, false) + key, err = s.CreatePreAuthKey(u.GetID().Or(0), true, false) } if err != nil { return err } - err = s.RunTailscaleUp(user, headscale.GetEndpoint(), key.GetKey()) + err = s.RunTailscaleUp(user, headscale.GetEndpoint(), key.GetKey().Or("")) if err != nil { return err } diff --git a/integration/scenario_test.go b/integration/scenario_test.go index ae264221..0fbd99d6 100644 --- a/integration/scenario_test.go +++ b/integration/scenario_test.go @@ -137,7 +137,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { err = scenario.RunTailscaleUp( user, headscale.GetEndpoint(), - key.GetKey(), + key.GetKey().Or(""), ) if err != nil { t.Fatalf("failed to login: %s", err) diff --git a/integration/tags_test.go b/integration/tags_test.go index fa4739a0..7645dc1d 100644 --- a/integration/tags_test.go +++ b/integration/tags_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + apiv1 "github.com/juanfont/headscale/gen/api/v1" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/integration/hsic" @@ -67,19 +67,19 @@ func tagsEqual(actual, expected []string) bool { } // assertNodeHasTagsWithCollect asserts that a node has exactly the expected tags (order-independent). -func assertNodeHasTagsWithCollect(c *assert.CollectT, node *v1.Node, expectedTags []string) { +func assertNodeHasTagsWithCollect(c *assert.CollectT, node *apiv1.Node, expectedTags []string) { actualTags := node.GetTags() sortedActual := append([]string{}, actualTags...) sortedExpected := append([]string{}, expectedTags...) sort.Strings(sortedActual) sort.Strings(sortedExpected) - assert.Equal(c, sortedExpected, sortedActual, "Node %s tags mismatch", node.GetName()) + assert.Equal(c, sortedExpected, sortedActual, "Node %s tags mismatch", node.GetName().Or("")) } // assertNodeHasNoTagsWithCollect asserts that a node has no tags. -func assertNodeHasNoTagsWithCollect(c *assert.CollectT, node *v1.Node) { - assert.Empty(c, node.GetTags(), "Node %s should have no tags, but has: %v", node.GetName(), node.GetTags()) +func assertNodeHasNoTagsWithCollect(c *assert.CollectT, node *apiv1.Node) { + assert.Empty(c, node.GetTags(), "Node %s should have no tags, but has: %v", node.GetName().Or(""), node.GetTags()) } // assertNodeSelfHasTagsWithCollect asserts that a client's self view has exactly the expected tags. @@ -148,7 +148,7 @@ func TestTagsAuthKeyWithTagRequestDifferentTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -164,7 +164,7 @@ func TestTagsAuthKeyWithTagRequestDifferentTag(t *testing.T) { require.NoError(t, err) // Login should fail because the advertised tags don't match the auth key's tags - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) // Document actual behavior - we expect this to fail if err != nil { @@ -222,7 +222,7 @@ func TestTagsAuthKeyWithTagNoAdvertiseFlag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -238,7 +238,7 @@ func TestTagsAuthKeyWithTagNoAdvertiseFlag(t *testing.T) { require.NoError(t, err) // Login with the tagged PreAuthKey - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for node to be registered and verify it has the key's tags @@ -294,7 +294,7 @@ func TestTagsAuthKeyWithTagCannotAddViaCLI(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -308,7 +308,7 @@ func TestTagsAuthKeyWithTagCannotAddViaCLI(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration @@ -328,7 +328,7 @@ func TestTagsAuthKeyWithTagCannotAddViaCLI(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=tag:valid-owned,tag:second", } _, stderr, err := client.Execute(command) @@ -394,7 +394,7 @@ func TestTagsAuthKeyWithTagCannotChangeViaCLI(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -408,7 +408,7 @@ func TestTagsAuthKeyWithTagCannotChangeViaCLI(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration @@ -424,7 +424,7 @@ func TestTagsAuthKeyWithTagCannotChangeViaCLI(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=tag:second", } _, stderr, err := client.Execute(command) @@ -490,7 +490,7 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, true, false, []string{"tag:valid-owned"}) @@ -504,7 +504,7 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration and get node ID @@ -516,7 +516,7 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"}) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -549,7 +549,7 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--force-reauth", } //nolint:errcheck // Intentionally ignoring error - we check results below @@ -617,7 +617,7 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, true, false, []string{"tag:valid-owned"}) @@ -631,7 +631,7 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration and get node ID @@ -643,7 +643,7 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -672,7 +672,7 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=tag:valid-owned", } _, stderr, err := client.Execute(command) @@ -739,7 +739,7 @@ func TestTagsAuthKeyWithoutTagCannotRequestTags(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, false, false) @@ -755,7 +755,7 @@ func TestTagsAuthKeyWithoutTagCannotRequestTags(t *testing.T) { require.NoError(t, err) // Login should fail because the auth key has no tags - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 3.1 PASS: Registration correctly rejected: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -810,7 +810,7 @@ func TestTagsAuthKeyWithoutTagRegisterNoTags(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, false, false) @@ -824,7 +824,7 @@ func TestTagsAuthKeyWithoutTagRegisterNoTags(t *testing.T) { require.NoError(t, err) // Login should succeed - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Verify node has no tags @@ -879,7 +879,7 @@ func TestTagsAuthKeyWithoutTagCannotAddViaCLI(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, true, false) @@ -893,7 +893,7 @@ func TestTagsAuthKeyWithoutTagCannotAddViaCLI(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration @@ -913,7 +913,7 @@ func TestTagsAuthKeyWithoutTagCannotAddViaCLI(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=tag:valid-owned", } _, stderr, err := client.Execute(command) @@ -978,7 +978,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, true, false) @@ -992,7 +992,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration and get node ID @@ -1004,7 +1004,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) assertNodeHasNoTagsWithCollect(c, nodes[0]) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -1034,7 +1034,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--reset", } _, stderr, err := client.Execute(command) @@ -1098,7 +1098,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T) userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, true, false) @@ -1112,7 +1112,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T) require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration and get node ID @@ -1124,7 +1124,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T) assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -1153,7 +1153,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T) command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=", } _, stderr, err := client.Execute(command) @@ -1217,7 +1217,7 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, true, false) @@ -1231,7 +1231,7 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) { require.NoError(t, err) // Initial login - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for initial registration and get node ID @@ -1243,7 +1243,7 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -1272,7 +1272,7 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) { command := []string{ "tailscale", "up", "--login-server=" + headscale.GetEndpoint(), - "--authkey=" + authKey.GetKey(), + "--authkey=" + authKey.GetKey().Or(""), "--advertise-tags=tag:valid-owned", } _, stderr, err := client.Execute(command) @@ -1757,7 +1757,7 @@ func TestTagsUserLoginCLINoOpAfterAdminAssignment(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) t.Logf("Step 1: Node %d registered with tags: %v", nodeID, nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -1874,7 +1874,7 @@ func TestTagsUserLoginCLICannotRemoveAdminTags(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -1965,7 +1965,7 @@ func TestTagsAuthKeyWithTagRequestNonExistentTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -1981,7 +1981,7 @@ func TestTagsAuthKeyWithTagRequestNonExistentTag(t *testing.T) { require.NoError(t, err) // Login should fail because ANY advertise-tags is rejected for PreAuthKey registrations - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 2.7 PASS: Registration correctly rejected with error: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -2035,7 +2035,7 @@ func TestTagsAuthKeyWithTagRequestUnownedTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey with tag:valid-owned authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -2051,7 +2051,7 @@ func TestTagsAuthKeyWithTagRequestUnownedTag(t *testing.T) { require.NoError(t, err) // Login should fail because ANY advertise-tags is rejected for PreAuthKey registrations - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 2.8 PASS: Registration correctly rejected with error: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -2109,7 +2109,7 @@ func TestTagsAuthKeyWithoutTagRequestNonExistentTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, false, false) @@ -2125,7 +2125,7 @@ func TestTagsAuthKeyWithoutTagRequestNonExistentTag(t *testing.T) { require.NoError(t, err) // Login should fail because ANY advertise-tags is rejected for PreAuthKey registrations - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 3.7 PASS: Registration correctly rejected: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -2179,7 +2179,7 @@ func TestTagsAuthKeyWithoutTagRequestUnownedTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create an auth key WITHOUT tags authKey, err := scenario.CreatePreAuthKey(userID, false, false) @@ -2195,7 +2195,7 @@ func TestTagsAuthKeyWithoutTagRequestUnownedTag(t *testing.T) { require.NoError(t, err) // Login should fail because ANY advertise-tags is rejected for PreAuthKey registrations - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 3.8 PASS: Registration correctly rejected: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -2253,7 +2253,7 @@ func TestTagsAdminAPICannotSetNonExistentTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey to register a node authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -2266,7 +2266,7 @@ func TestTagsAdminAPICannotSetNonExistentTag(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for registration and get node ID @@ -2278,7 +2278,7 @@ func TestTagsAdminAPICannotSetNonExistentTag(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration") @@ -2325,7 +2325,7 @@ func TestTagsAdminAPICanSetUnownedTag(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey to register a node authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -2338,7 +2338,7 @@ func TestTagsAdminAPICanSetUnownedTag(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for registration and get node ID @@ -2350,7 +2350,7 @@ func TestTagsAdminAPICanSetUnownedTag(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration") @@ -2413,7 +2413,7 @@ func TestTagsAdminAPICannotRemoveAllTags(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey to register a node authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -2426,7 +2426,7 @@ func TestTagsAdminAPICannotRemoveAllTags(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for registration and get node ID @@ -2438,7 +2438,7 @@ func TestTagsAdminAPICannotRemoveAllTags(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration") @@ -2560,7 +2560,7 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"}) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration") @@ -2735,7 +2735,7 @@ func TestTagsAdminAPICannotSetInvalidFormat(t *testing.T) { userMap, err := headscale.MapUsers() require.NoError(t, err) - userID := userMap[tagTestUser].GetId() + userID := userMap[tagTestUser].GetID().Or(0) // Create a tagged PreAuthKey to register a node authKey, err := scenario.CreatePreAuthKeyWithTags(userID, false, false, []string{"tag:valid-owned"}) @@ -2748,7 +2748,7 @@ func TestTagsAdminAPICannotSetInvalidFormat(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for registration and get node ID @@ -2760,7 +2760,7 @@ func TestTagsAdminAPICannotSetInvalidFormat(t *testing.T) { assert.Len(c, nodes, 1) if len(nodes) == 1 { - nodeID = nodes[0].GetId() + nodeID = nodes[0].GetID().Or(0) t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration") @@ -2864,9 +2864,9 @@ func TestTagsUserLoginReauthWithEmptyTagsRemovesAllTags(t *testing.T) { if len(nodes) == 1 { node := nodes[0] - initialNodeID = node.GetId() + initialNodeID = node.GetID().Or(0) t.Logf("Initial state - Node ID: %d, Tags: %v, User: %s", - node.GetId(), node.GetTags(), node.GetUser().GetName()) + node.GetID().Or(0), node.GetTags(), node.GetUser().Value.Name.Or("")) // Verify node has the expected tags assertNodeHasTagsWithCollect(c, node, []string{"tag:valid-owned", "tag:second"}) @@ -2927,24 +2927,24 @@ func TestTagsUserLoginReauthWithEmptyTagsRemovesAllTags(t *testing.T) { if len(nodes) >= 1 { node := nodes[0] t.Logf("After reauth - Node ID: %d, Tags: %v, User: %s", - node.GetId(), node.GetTags(), node.GetUser().GetName()) + node.GetID().Or(0), node.GetTags(), node.GetUser().Value.Name.Or("")) // Assert: Node should have NO tags assertNodeHasNoTagsWithCollect(c, node) // Assert: Node should be owned by the user (not tagged-devices) - assert.Equal(c, tagTestUser, node.GetUser().GetName(), + assert.Equal(c, tagTestUser, node.GetUser().Value.Name.Or(""), "Node ownership should return to user %s after untagging", tagTestUser) // Verify the node ID is still the same (not a new registration) - assert.Equal(c, initialNodeID, node.GetId(), + assert.Equal(c, initialNodeID, node.GetID().Or(0), "Node ID should remain the same after reauth") - if len(node.GetTags()) == 0 && node.GetUser().GetName() == tagTestUser { + if len(node.GetTags()) == 0 && node.GetUser().Value.Name.Or("") == tagTestUser { t.Logf("Test #2979 (%s) PASS: Node successfully untagged and ownership returned to user", tc.name) } else { t.Logf("Test #2979 (%s) FAIL: Expected no tags and user=%s, got tags=%v user=%s", - tc.name, tagTestUser, node.GetTags(), node.GetUser().GetName()) + tc.name, tagTestUser, node.GetTags(), node.GetUser().Value.Name.Or("")) } } }, integrationutil.HAConvergeTimeout, 1*time.Second, "verifying tags removed and ownership returned") @@ -3005,7 +3005,7 @@ func TestTagsAuthKeyWithoutUserInheritsTags(t *testing.T) { require.NoError(t, err) // Login with the tags-only auth key - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) // Wait for node to be registered and verify it has the key's tags @@ -3076,7 +3076,7 @@ func TestTagsAuthKeyWithoutUserRejectsAdvertisedTags(t *testing.T) { require.NoError(t, err) // Login should fail because ANY advertise-tags is rejected for PreAuthKey registrations - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) if err != nil { t.Logf("Test 5.2 PASS: Registration correctly rejected with error: %v", err) assert.ErrorContains(t, err, "requested tags") @@ -3142,7 +3142,7 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) { ) require.NoError(t, err) - err = client.Login(headscale.GetEndpoint(), authKey.GetKey()) + err = client.Login(headscale.GetEndpoint(), authKey.GetKey().Or("")) require.NoError(t, err) err = client.WaitForRunning(integrationutil.PeerSyncTimeout()) @@ -3156,7 +3156,7 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) { if len(nodes) == 1 { assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"}) - t.Logf("Initial state - Node ID: %d, Tags: %v", nodes[0].GetId(), nodes[0].GetTags()) + t.Logf("Initial state - Node ID: %d, Tags: %v", nodes[0].GetID().Or(0), nodes[0].GetTags()) } }, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "node should be tagged initially") @@ -3196,10 +3196,10 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) { if len(nodes) == 1 { assertNodeHasNoTagsWithCollect(c, nodes[0]) - assert.Equal(c, tagTestUser, nodes[0].GetUser().GetName(), + assert.Equal(c, tagTestUser, nodes[0].GetUser().Value.Name.Or(""), "Node ownership should be returned to user after untagging") t.Logf("After conversion - Node ID: %d, Tags: %v, User: %s", - nodes[0].GetId(), nodes[0].GetTags(), nodes[0].GetUser().GetName()) + nodes[0].GetID().Or(0), nodes[0].GetTags(), nodes[0].GetUser().Value.Name.Or("")) } }, integrationutil.HAConvergeTimeout, 1*time.Second, "node should be user-owned after conversion via CLI register") } diff --git a/integration/tsric_test.go b/integration/tsric_test.go index 757e08b6..1921c30f 100644 --- a/integration/tsric_test.go +++ b/integration/tsric_test.go @@ -73,8 +73,8 @@ func TestTailscaleRustAxum(t *testing.T) { var userID uint64 for _, u := range users { - if u.GetName() == "user1" { //nolint:goconst - userID = u.GetId() + if u.GetName().Or("") == "user1" { //nolint:goconst + userID = u.GetID().Or(0) break } @@ -101,7 +101,7 @@ func TestTailscaleRustAxum(t *testing.T) { tsrsOpts := []tsric.Option{ tsric.WithNetwork(network), tsric.WithHeadscaleURL(headscaleEndpoint), - tsric.WithAuthKey(pak.GetKey()), + tsric.WithAuthKey(pak.GetKey().Or("")), tsric.WithExtraHosts([]string{headscaleHostname + ":" + headscaleIP}), } @@ -142,7 +142,7 @@ func TestTailscaleRustAxum(t *testing.T) { // Find the tailscale-rs node by hostname prefix for _, n := range nodes { - if strings.HasPrefix(n.GetGivenName(), "tsrs-") { + if strings.HasPrefix(n.GetGivenName().Or(""), "tsrs-") { addrs := n.GetIpAddresses() if len(addrs) > 0 { rustNodeIPv4 = addrs[0] @@ -152,7 +152,7 @@ func TestTailscaleRustAxum(t *testing.T) { rustNodeIPv6 = addrs[1] } - rustNodeName = n.GetGivenName() + rustNodeName = n.GetGivenName().Or("") } }