mirror of
https://github.com/juanfont/headscale.git
synced 2026-08-07 07:48:44 +09:00
integration: port tests to the ogen API types
Decode CLI/HTTP output into apiv1 (ogen) types instead of the proto types, matching the new wire format. No proto package is imported outside gen/go now.
This commit is contained in:
+59
-51
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
+196
-196
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+11
-11
@@ -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
|
||||
|
||||
+57
-55
@@ -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-<rand>
|
||||
// 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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+10
-10
@@ -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)
|
||||
}
|
||||
|
||||
+26
-25
@@ -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 {
|
||||
|
||||
+148
-129
@@ -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.
|
||||
|
||||
+10
-10
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+78
-78
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user