mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-25 01:34:54 +09:00
cli: migrate the CLI and integration tests to the v1 HTTP API
Replace the gRPC client with the generated HTTP client across every command: locally over the unix socket without auth (matching the previous local gRPC socket), remotely over TLS with a Bearer API key. Output rendering and integration tests move to the HTTP client types; the transport changes, the assertions do not.
This commit is contained in:
+137
-137
@@ -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"
|
||||
clientv1 "github.com/juanfont/headscale/gen/client/v1"
|
||||
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/integration/hsic"
|
||||
@@ -86,26 +86,26 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
|
||||
listUsers, err := headscale.ListUsers()
|
||||
require.NoError(t, err)
|
||||
|
||||
want := []*v1.User{
|
||||
want := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
ProviderId: scenario.mockOIDC.Issuer() + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Id: "3",
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Id: "4",
|
||||
Name: "user2",
|
||||
Email: "", // Unverified
|
||||
Provider: "oidc",
|
||||
@@ -114,10 +114,10 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.User{}, "CreatedAt")); diff != "" {
|
||||
t.Fatalf("unexpected users: %s", diff)
|
||||
}
|
||||
}
|
||||
@@ -245,34 +245,34 @@ func TestOIDC024UserCreation(t *testing.T) {
|
||||
emailVerified bool
|
||||
cliUsers []string
|
||||
oidcUsers []string
|
||||
want func(iss string) []*v1.User
|
||||
want func(iss string) []*clientv1.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) []*clientv1.User {
|
||||
return []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
ProviderId: iss + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Id: "3",
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Id: "4",
|
||||
Name: "user2",
|
||||
Email: "user2@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -286,26 +286,26 @@ 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) []*clientv1.User {
|
||||
return []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user1",
|
||||
Provider: "oidc",
|
||||
ProviderId: iss + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Id: "3",
|
||||
Name: "user2",
|
||||
Email: "user2@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Id: "4",
|
||||
Name: "user2",
|
||||
Provider: "oidc",
|
||||
ProviderId: iss + "/user2",
|
||||
@@ -318,26 +318,26 @@ 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) []*clientv1.User {
|
||||
return []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1.headscale.net",
|
||||
Email: "user1.headscale.net@test.no",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user1",
|
||||
Provider: "oidc",
|
||||
ProviderId: iss + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Id: "3",
|
||||
Name: "user2.headscale.net",
|
||||
Email: "user2.headscale.net@test.no",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Id: "4",
|
||||
Name: "user2",
|
||||
Provider: "oidc",
|
||||
ProviderId: 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 mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(want, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.User{}, "CreatedAt")); diff != "" {
|
||||
t.Errorf("unexpected users: %s", diff)
|
||||
}
|
||||
})
|
||||
@@ -509,9 +509,9 @@ 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 := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -520,17 +520,17 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.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 []*clientv1.Node
|
||||
|
||||
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
|
||||
var err error
|
||||
@@ -593,16 +593,16 @@ 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 := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
ProviderId: scenario.mockOIDC.Issuer() + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user2",
|
||||
Email: "user2@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -611,15 +611,15 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.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 []*clientv1.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].MachineKey, listNodesAfterNewUserLogin[0].MachineKey, "Machine key should be preserved from original node")
|
||||
assert.Equal(ct, listNodesAfterNewUserLogin[0].MachineKey, listNodesAfterNewUserLogin[1].MachineKey, "Both nodes should share the same machine key")
|
||||
assert.NotEqual(ct, listNodesAfterNewUserLogin[0].NodeKey, listNodesAfterNewUserLogin[1].NodeKey, "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.User.Id == "2" { // user2
|
||||
activeUser2NodeID = types.NodeID(mustParseID(node.Id))
|
||||
t.Logf("Active user2 node: %d (User: %s)", mustParseID(node.Id), node.User.Name)
|
||||
|
||||
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.NotEmpty(ct, node.User.Id, "Node %d should have a valid user before logout", i)
|
||||
assert.NotEmpty(ct, node.MachineKey, "Node %d should have a valid machine key before logout", i)
|
||||
t.Logf("Pre-logout node %d: User=%s, MachineKey=%s", i, node.User.Name, node.MachineKey[: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.NotEmpty(ct, node.User.Id, "Node %d should still have a valid user after user2 logout", i)
|
||||
assert.NotEmpty(ct, node.MachineKey, "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.User.Name, node.MachineKey[:16]+"...")
|
||||
}
|
||||
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating node persistence and integrity after user2 logout")
|
||||
|
||||
@@ -761,16 +761,16 @@ 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 := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
ProviderId: scenario.mockOIDC.Issuer() + "/user1",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Id: "2",
|
||||
Name: "user2",
|
||||
Email: "user2@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -779,15 +779,15 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.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 []*clientv1.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].MachineKey, listNodesAfterNewUserLogin[0].MachineKey, "Original user1 machine key should match user1 node after user switch")
|
||||
assert.Equal(ct, listNodes[0].NodeKey, listNodesAfterNewUserLogin[0].NodeKey, "Original user1 node key should match user1 node after user switch")
|
||||
assert.Equal(ct, listNodes[0].Id, listNodesAfterNewUserLogin[0].Id, "Original user1 node ID should match user1 node after user switch")
|
||||
assert.Equal(ct, listNodes[0].MachineKey, listNodesAfterNewUserLogin[1].MachineKey, "User1 and user2 nodes should share the same machine key")
|
||||
assert.NotEqual(ct, listNodes[0].Id, listNodesAfterNewUserLogin[1].Id, "User1 and user2 nodes should have different node IDs")
|
||||
assert.NotEqual(ct, listNodes[0].User.Id, listNodesAfterNewUserLogin[1].User.Id, "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].MachineKey, listNodesAfterLoggingBackIn[0].MachineKey, "Machine key should remain consistent after user1 relogin")
|
||||
assert.NotEqual(ct, listNodes[0].NodeKey, listNodesAfterLoggingBackIn[0].NodeKey, "Node key should be regenerated after user1 relogin")
|
||||
assert.Equal(ct, listNodes[0].Id, listNodesAfterLoggingBackIn[0].Id, "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].MachineKey, listNodesAfterLoggingBackIn[1].MachineKey, "Both final nodes should share the same machine key")
|
||||
assert.NotEqual(ct, listNodesAfterLoggingBackIn[0].NodeKey, listNodesAfterLoggingBackIn[1].NodeKey, "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.User.Id == "1" { // user1
|
||||
activeUser1NodeID = types.NodeID(mustParseID(node.Id))
|
||||
t.Logf("Active user1 node after relogin: %d (User: %s)", mustParseID(node.Id), node.User.Name)
|
||||
|
||||
break
|
||||
}
|
||||
@@ -942,9 +942,9 @@ func TestOIDCFollowUpUrl(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, listUsers, 1)
|
||||
|
||||
wantUsers := []*v1.User{
|
||||
wantUsers := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -954,15 +954,15 @@ func TestOIDCFollowUpUrl(t *testing.T) {
|
||||
|
||||
sort.Slice(
|
||||
listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
},
|
||||
)
|
||||
|
||||
if diff := cmp.Diff(
|
||||
wantUsers,
|
||||
listUsers,
|
||||
cmpopts.IgnoreUnexported(v1.User{}),
|
||||
cmpopts.IgnoreFields(v1.User{}, "CreatedAt"),
|
||||
cmpopts.IgnoreUnexported(clientv1.User{}),
|
||||
cmpopts.IgnoreFields(clientv1.User{}, "CreatedAt"),
|
||||
); diff != "" {
|
||||
t.Fatalf("unexpected users: %s", diff)
|
||||
}
|
||||
@@ -1051,9 +1051,9 @@ func TestOIDCMultipleOpenedLoginUrls(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, listUsers, 1)
|
||||
|
||||
wantUsers := []*v1.User{
|
||||
wantUsers := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -1063,15 +1063,15 @@ func TestOIDCMultipleOpenedLoginUrls(t *testing.T) {
|
||||
|
||||
sort.Slice(
|
||||
listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
},
|
||||
)
|
||||
|
||||
if diff := cmp.Diff(
|
||||
wantUsers,
|
||||
listUsers,
|
||||
cmpopts.IgnoreUnexported(v1.User{}),
|
||||
cmpopts.IgnoreFields(v1.User{}, "CreatedAt"),
|
||||
cmpopts.IgnoreUnexported(clientv1.User{}),
|
||||
cmpopts.IgnoreFields(clientv1.User{}, "CreatedAt"),
|
||||
); diff != "" {
|
||||
t.Fatalf("unexpected users: %s", diff)
|
||||
}
|
||||
@@ -1159,9 +1159,9 @@ 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 := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -1170,17 +1170,17 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.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 []*clientv1.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].MachineKey
|
||||
initialNodeKey := initialNodes[0].NodeKey
|
||||
initialNodeID := initialNodes[0].Id
|
||||
|
||||
// Logout user1
|
||||
err = ts.Logout()
|
||||
@@ -1262,9 +1262,9 @@ 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 := []*clientv1.User{
|
||||
{
|
||||
Id: 1,
|
||||
Id: "1",
|
||||
Name: "user1",
|
||||
Email: "user1@headscale.net",
|
||||
Provider: "oidc",
|
||||
@@ -1273,15 +1273,15 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
|
||||
}
|
||||
|
||||
sort.Slice(listUsers, func(i, j int) bool {
|
||||
return listUsers[i].GetId() < listUsers[j].GetId()
|
||||
return mustParseID(listUsers[i].Id) < mustParseID(listUsers[j].Id)
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
|
||||
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(clientv1.User{}), cmpopts.IgnoreFields(clientv1.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 []*clientv1.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.MachineKey, "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.Id, "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.NodeKey, "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(mustParseID(finalNodes[0].Id))
|
||||
|
||||
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.NotNil(ct, node.Expiry, "Expiry should be set after OIDC login")
|
||||
|
||||
if node.GetExpiry() != nil {
|
||||
expiryTime := node.GetExpiry().AsTime()
|
||||
if node.Expiry != nil {
|
||||
expiryTime := *node.Expiry
|
||||
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.NotNil(ct, node.Expiry, "Expiry should NOT be nil after restart")
|
||||
|
||||
if node.GetExpiry() != nil {
|
||||
expiryTime := node.GetExpiry().AsTime()
|
||||
if node.Expiry != nil {
|
||||
expiryTime := *node.Expiry
|
||||
|
||||
// This is the bug check - expiry should NOT be zero time
|
||||
assert.False(ct, expiryTime.IsZero(),
|
||||
@@ -1567,9 +1567,9 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
|
||||
assert.Len(ct, nodes, 1)
|
||||
|
||||
gatewayNode := nodes[0]
|
||||
gatewayNodeID = gatewayNode.GetId()
|
||||
assert.Len(ct, gatewayNode.GetAvailableRoutes(), 1)
|
||||
assert.Contains(ct, gatewayNode.GetAvailableRoutes(), advertiseRoute)
|
||||
gatewayNodeID = mustParseID(gatewayNode.Id)
|
||||
assert.Len(ct, gatewayNode.AvailableRoutes, 1)
|
||||
assert.Contains(ct, gatewayNode.AvailableRoutes, advertiseRoute)
|
||||
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "route advertisement should propagate to headscale")
|
||||
|
||||
// Approve the advertised route
|
||||
@@ -1586,8 +1586,8 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
|
||||
assert.Len(ct, nodes, 1)
|
||||
|
||||
gatewayNode := nodes[0]
|
||||
assert.Len(ct, gatewayNode.GetApprovedRoutes(), 1)
|
||||
assert.Contains(ct, gatewayNode.GetApprovedRoutes(), advertiseRoute)
|
||||
assert.Len(ct, gatewayNode.ApprovedRoutes, 1)
|
||||
assert.Contains(ct, gatewayNode.ApprovedRoutes, advertiseRoute)
|
||||
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "route approval should propagate to headscale")
|
||||
|
||||
// NOW create the OIDC user by having them join
|
||||
@@ -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 *clientv1.User
|
||||
|
||||
for _, user := range users {
|
||||
if user.GetName() == "gateway" && user.GetProvider() == "" {
|
||||
if user.Name == "gateway" && user.Provider == "" {
|
||||
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.Name)
|
||||
}
|
||||
|
||||
// Find OIDC user
|
||||
var oidcUserFound *v1.User
|
||||
var oidcUserFound *clientv1.User
|
||||
|
||||
for _, user := range users {
|
||||
if user.GetName() == "oidcuser" && user.GetProvider() == "oidc" {
|
||||
if user.Name == "oidcuser" && user.Provider == "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.Name)
|
||||
assert.Equal(ct, "oidcuser@headscale.net", oidcUserFound.Email)
|
||||
}
|
||||
}, 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 *clientv1.Node
|
||||
|
||||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
nodes, err := headscale.ListNodes()
|
||||
@@ -1844,21 +1844,21 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
|
||||
if len(nodes) == 1 {
|
||||
initialNode = nodes[0]
|
||||
// Check: 1 announced, 1 approved, 1 serving (subnet route)
|
||||
assert.Lenf(c, initialNode.GetAvailableRoutes(), 1,
|
||||
"Node should have 1 available route, got %v", initialNode.GetAvailableRoutes())
|
||||
assert.Lenf(c, initialNode.GetApprovedRoutes(), 1,
|
||||
"Node should have 1 approved route, got %v", initialNode.GetApprovedRoutes())
|
||||
assert.Lenf(c, initialNode.GetSubnetRoutes(), 1,
|
||||
"Node should have 1 serving (subnet) route, got %v - THIS IS THE BUG if empty", initialNode.GetSubnetRoutes())
|
||||
assert.Contains(c, initialNode.GetSubnetRoutes(), advertiseRoute,
|
||||
assert.Lenf(c, initialNode.AvailableRoutes, 1,
|
||||
"Node should have 1 available route, got %v", initialNode.AvailableRoutes)
|
||||
assert.Lenf(c, initialNode.ApprovedRoutes, 1,
|
||||
"Node should have 1 approved route, got %v", initialNode.ApprovedRoutes)
|
||||
assert.Lenf(c, initialNode.SubnetRoutes, 1,
|
||||
"Node should have 1 serving (subnet) route, got %v - THIS IS THE BUG if empty", initialNode.SubnetRoutes)
|
||||
assert.Contains(c, initialNode.SubnetRoutes, advertiseRoute,
|
||||
"Subnet routes should contain %s", advertiseRoute)
|
||||
}
|
||||
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "initial route should be serving")
|
||||
|
||||
require.NotNil(t, initialNode, "Initial node should be found")
|
||||
initialNodeID := initialNode.GetId()
|
||||
t.Logf("Initial node ID: %d, Available: %v, Approved: %v, Serving: %v",
|
||||
initialNodeID, initialNode.GetAvailableRoutes(), initialNode.GetApprovedRoutes(), initialNode.GetSubnetRoutes())
|
||||
initialNodeID := initialNode.Id
|
||||
t.Logf("Initial node ID: %s, Available: %v, Approved: %v, Serving: %v",
|
||||
initialNodeID, initialNode.AvailableRoutes, initialNode.ApprovedRoutes, initialNode.SubnetRoutes)
|
||||
|
||||
// Step 2: Logout
|
||||
t.Logf("Step 2: Logging out at %s", time.Now().Format(TimestampFormat))
|
||||
@@ -1911,23 +1911,23 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
|
||||
if len(nodes) == 1 {
|
||||
node := nodes[0]
|
||||
t.Logf("After relogin - Available: %v, Approved: %v, Serving: %v",
|
||||
node.GetAvailableRoutes(), node.GetApprovedRoutes(), node.GetSubnetRoutes())
|
||||
node.AvailableRoutes, node.ApprovedRoutes, node.SubnetRoutes)
|
||||
|
||||
// This is where issue #2896 manifests:
|
||||
// - Available shows the route (from [tailcfg.Hostinfo.RoutableIPs])
|
||||
// - Approved shows the route (from [tailcfg.Node.ApprovedRoutes])
|
||||
// - BUT Serving ([tailcfg.Node.SubnetRoutes]/[ipnstate.PeerStatus.PrimaryRoutes]) is EMPTY!
|
||||
assert.Lenf(c, node.GetAvailableRoutes(), 1,
|
||||
"Node should have 1 available route after relogin, got %v", node.GetAvailableRoutes())
|
||||
assert.Lenf(c, node.GetApprovedRoutes(), 1,
|
||||
"Node should have 1 approved route after relogin, got %v", node.GetApprovedRoutes())
|
||||
assert.Lenf(c, node.GetSubnetRoutes(), 1,
|
||||
"BUG #2896: Node should have 1 SERVING route after relogin, got %v", node.GetSubnetRoutes())
|
||||
assert.Contains(c, node.GetSubnetRoutes(), advertiseRoute,
|
||||
assert.Lenf(c, node.AvailableRoutes, 1,
|
||||
"Node should have 1 available route after relogin, got %v", node.AvailableRoutes)
|
||||
assert.Lenf(c, node.ApprovedRoutes, 1,
|
||||
"Node should have 1 approved route after relogin, got %v", node.ApprovedRoutes)
|
||||
assert.Lenf(c, node.SubnetRoutes, 1,
|
||||
"BUG #2896: Node should have 1 SERVING route after relogin, got %v", node.SubnetRoutes)
|
||||
assert.Contains(c, node.SubnetRoutes, advertiseRoute,
|
||||
"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.Id,
|
||||
"Node ID should be preserved after same-user relogin")
|
||||
}
|
||||
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll,
|
||||
|
||||
Reference in New Issue
Block a user