Compare commits

...

6 commits

Author SHA1 Message Date
Kristoffer Dalby
5860b9d652
Merge c59144a287 into dc17b4d378 2024-11-22 17:01:29 +00:00
Kristoffer Dalby
c59144a287
only set username and email if valid
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-22 18:01:19 +01:00
Kristoffer Dalby
c905f0499b
ensure provider id is found out of order
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-22 18:00:23 +01:00
Kristoffer Dalby
697ec7f46c
resolve user identifier to stable ID
currently, the policy approach node to user matching
with a quite naive approach looking at the username
provided in the policy and matched it with the username
on the nodes. This worked ok as long as usernames were
unique and did not change.

As usernames are no longer guarenteed to be unique in
an OIDC environment we cant rely on this.

This changes the mechanism that matches the user string
(now user token) with nodes:

- first find all potential users by looking up:
  - database ID
  - provider ID (OIDC)
  - username/email

If more than one user is matching, then the query is
rejected, and zero matching nodes are returned.

When a single user is found, the node is matched against
the User database ID, which are also present on the actual
node.

This means that from this commit, users can use the following
to identify users in the policy:
- provider identity (iss + sub)
- username
- email
- database id

There are more changes coming to this, so it is not recommended
to start using any of these new abilities, with the exception
of email, which will not change since it includes an @.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-22 18:00:22 +01:00
nblock
dc17b4d378
Documentation dependencies (#2252)
Some checks are pending
Build / build (push) Waiting to run
Build documentation / build (push) Waiting to run
Build documentation / deploy (push) Blocked by required conditions
Tests / test (push) Waiting to run
* Use a trailing slash
recommended by mkdocs-material

* Update doc requirements
Let mkdocs-material resolve its imaging dependencies (cairosvg and
pillow) and fix a dependabot warning along the way.

Reference compatible versions by major.minor.
2024-11-22 16:52:36 +00:00
Kristoffer Dalby
a6b19e85db
more linter fixups (#2212)
* linter fixes

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* conf

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update nix hash

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-22 15:54:58 +00:00
24 changed files with 1067 additions and 547 deletions

View file

@ -27,6 +27,7 @@ linters:
- nolintlint - nolintlint
- musttag # causes issues with imported libs - musttag # causes issues with imported libs
- depguard - depguard
- exportloopref
# We should strive to enable these: # We should strive to enable these:
- wrapcheck - wrapcheck
@ -56,9 +57,14 @@ linters-settings:
- ok - ok
- c - c
- tt - tt
- tx
- rx
gocritic: gocritic:
disabled-checks: disabled-checks:
- appendAssign - appendAssign
# TODO(kradalby): Remove this # TODO(kradalby): Remove this
- ifElseChain - ifElseChain
nlreturn:
block-size: 4

View file

@ -1,7 +1,5 @@
cairosvg~=2.7.1 mkdocs-include-markdown-plugin~=7.1
mkdocs-include-markdown-plugin~=6.2.2 mkdocs-macros-plugin~=1.3
mkdocs-macros-plugin~=1.2.0 mkdocs-material[imaging]~=9.5
mkdocs-material~=9.5.18 mkdocs-minify-plugin~=0.7
mkdocs-minify-plugin~=0.7.1 mkdocs-redirects~=1.2
mkdocs-redirects~=1.2.1
pillow~=10.1.0

View file

@ -32,7 +32,7 @@
# When updating go.mod or go.sum, a new sha will need to be calculated, # When updating go.mod or go.sum, a new sha will need to be calculated,
# update this if you have a mismatch after doing a change to thos files. # update this if you have a mismatch after doing a change to thos files.
vendorHash = "sha256-CMkYTRjmhvTTrB7JbLj0cj9VEyzpG0iUWXkaOagwYTk="; vendorHash = "sha256-Qoqu2k4vvnbRFLmT/v8lI+HCEWqJsHFs8uZRfNmwQpo=";
subPackages = ["cmd/headscale"]; subPackages = ["cmd/headscale"];

View file

@ -1029,14 +1029,18 @@ func (h *Headscale) loadACLPolicy() error {
if err != nil { if err != nil {
return fmt.Errorf("loading nodes from database to validate policy: %w", err) return fmt.Errorf("loading nodes from database to validate policy: %w", err)
} }
users, err := h.db.ListUsers()
if err != nil {
return fmt.Errorf("loading users from database to validate policy: %w", err)
}
_, err = pol.CompileFilterRules(nodes) _, err = pol.CompileFilterRules(users, nodes)
if err != nil { if err != nil {
return fmt.Errorf("verifying policy rules: %w", err) return fmt.Errorf("verifying policy rules: %w", err)
} }
if len(nodes) > 0 { if len(nodes) > 0 {
_, err = pol.CompileSSHPolicy(nodes[0], nodes) _, err = pol.CompileSSHPolicy(nodes[0], users, nodes)
if err != nil { if err != nil {
return fmt.Errorf("verifying SSH rules: %w", err) return fmt.Errorf("verifying SSH rules: %w", err)
} }

View file

@ -16,6 +16,7 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm" "gorm.io/gorm"
"zgo.at/zcache/v2" "zgo.at/zcache/v2"
) )
@ -44,7 +45,7 @@ func TestMigrations(t *testing.T) {
routes, err := Read(h.DB, func(rx *gorm.DB) (types.Routes, error) { routes, err := Read(h.DB, func(rx *gorm.DB) (types.Routes, error) {
return GetRoutes(rx) return GetRoutes(rx)
}) })
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, routes, 10) assert.Len(t, routes, 10)
want := types.Routes{ want := types.Routes{
@ -70,7 +71,7 @@ func TestMigrations(t *testing.T) {
routes, err := Read(h.DB, func(rx *gorm.DB) (types.Routes, error) { routes, err := Read(h.DB, func(rx *gorm.DB) (types.Routes, error) {
return GetRoutes(rx) return GetRoutes(rx)
}) })
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, routes, 4) assert.Len(t, routes, 4)
want := types.Routes{ want := types.Routes{
@ -132,7 +133,7 @@ func TestMigrations(t *testing.T) {
return append(kratest, testkra...), nil return append(kratest, testkra...), nil
}) })
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, keys, 5) assert.Len(t, keys, 5)
want := []types.PreAuthKey{ want := []types.PreAuthKey{
@ -177,7 +178,7 @@ func TestMigrations(t *testing.T) {
nodes, err := Read(h.DB, func(rx *gorm.DB) (types.Nodes, error) { nodes, err := Read(h.DB, func(rx *gorm.DB) (types.Nodes, error) {
return ListNodes(rx) return ListNodes(rx)
}) })
assert.NoError(t, err) require.NoError(t, err)
for _, node := range nodes { for _, node := range nodes {
assert.Falsef(t, node.MachineKey.IsZero(), "expected non zero machinekey") assert.Falsef(t, node.MachineKey.IsZero(), "expected non zero machinekey")

View file

@ -12,6 +12,7 @@ import (
"github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/types/ptr" "tailscale.com/types/ptr"
) )
@ -457,7 +458,12 @@ func TestBackfillIPAddresses(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
db := tt.dbFunc() db := tt.dbFunc()
alloc, err := NewIPAllocator(db, tt.prefix4, tt.prefix6, types.IPAllocationStrategySequential) alloc, err := NewIPAllocator(
db,
tt.prefix4,
tt.prefix6,
types.IPAllocationStrategySequential,
)
if err != nil { if err != nil {
t.Fatalf("failed to set up ip alloc: %s", err) t.Fatalf("failed to set up ip alloc: %s", err)
} }
@ -482,24 +488,29 @@ func TestBackfillIPAddresses(t *testing.T) {
} }
func TestIPAllocatorNextNoReservedIPs(t *testing.T) { func TestIPAllocatorNextNoReservedIPs(t *testing.T) {
alloc, err := NewIPAllocator(db, ptr.To(tsaddr.CGNATRange()), ptr.To(tsaddr.TailscaleULARange()), types.IPAllocationStrategySequential) alloc, err := NewIPAllocator(
db,
ptr.To(tsaddr.CGNATRange()),
ptr.To(tsaddr.TailscaleULARange()),
types.IPAllocationStrategySequential,
)
if err != nil { if err != nil {
t.Fatalf("failed to set up ip alloc: %s", err) t.Fatalf("failed to set up ip alloc: %s", err)
} }
// Validate that we do not give out 100.100.100.100 // Validate that we do not give out 100.100.100.100
nextQuad100, err := alloc.next(na("100.100.100.99"), ptr.To(tsaddr.CGNATRange())) nextQuad100, err := alloc.next(na("100.100.100.99"), ptr.To(tsaddr.CGNATRange()))
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, na("100.100.100.101"), *nextQuad100) assert.Equal(t, na("100.100.100.101"), *nextQuad100)
// Validate that we do not give out fd7a:115c:a1e0::53 // Validate that we do not give out fd7a:115c:a1e0::53
nextQuad100v6, err := alloc.next(na("fd7a:115c:a1e0::52"), ptr.To(tsaddr.TailscaleULARange())) nextQuad100v6, err := alloc.next(na("fd7a:115c:a1e0::52"), ptr.To(tsaddr.TailscaleULARange()))
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, na("fd7a:115c:a1e0::54"), *nextQuad100v6) assert.Equal(t, na("fd7a:115c:a1e0::54"), *nextQuad100v6)
// Validate that we do not give out fd7a:115c:a1e0::53 // Validate that we do not give out fd7a:115c:a1e0::53
nextChrome, err := alloc.next(na("100.115.91.255"), ptr.To(tsaddr.CGNATRange())) nextChrome, err := alloc.next(na("100.115.91.255"), ptr.To(tsaddr.CGNATRange()))
t.Logf("chrome: %s", nextChrome.String()) t.Logf("chrome: %s", nextChrome.String())
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, na("100.115.94.0"), *nextChrome) assert.Equal(t, na("100.115.94.0"), *nextChrome)
} }

View file

@ -17,6 +17,7 @@ import (
"github.com/juanfont/headscale/hscontrol/util" "github.com/juanfont/headscale/hscontrol/util"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/check.v1" "gopkg.in/check.v1"
"gorm.io/gorm" "gorm.io/gorm"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
@ -255,10 +256,10 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
c.Assert(len(testPeers), check.Equals, 9) c.Assert(len(testPeers), check.Equals, 9)
adminRules, _, err := policy.GenerateFilterAndSSHRulesForTests(aclPolicy, adminNode, adminPeers) adminRules, _, err := policy.GenerateFilterAndSSHRulesForTests(aclPolicy, adminNode, adminPeers, []types.User{*stor[0].user, *stor[1].user})
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
testRules, _, err := policy.GenerateFilterAndSSHRulesForTests(aclPolicy, testNode, testPeers) testRules, _, err := policy.GenerateFilterAndSSHRulesForTests(aclPolicy, testNode, testPeers, []types.User{*stor[0].user, *stor[1].user})
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
peersOfAdminNode := policy.FilterNodesByACL(adminNode, adminPeers, adminRules) peersOfAdminNode := policy.FilterNodesByACL(adminNode, adminPeers, adminRules)
@ -558,17 +559,17 @@ func TestAutoApproveRoutes(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
adb, err := newTestDB() adb, err := newTestDB()
assert.NoError(t, err) require.NoError(t, err)
pol, err := policy.LoadACLPolicyFromBytes([]byte(tt.acl)) pol, err := policy.LoadACLPolicyFromBytes([]byte(tt.acl))
assert.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, pol) assert.NotNil(t, pol)
user, err := adb.CreateUser("test") user, err := adb.CreateUser("test")
assert.NoError(t, err) require.NoError(t, err)
pak, err := adb.CreatePreAuthKey(user.Name, false, false, nil, nil) pak, err := adb.CreatePreAuthKey(user.Name, false, false, nil, nil)
assert.NoError(t, err) require.NoError(t, err)
nodeKey := key.NewNode() nodeKey := key.NewNode()
machineKey := key.NewMachine() machineKey := key.NewMachine()
@ -590,21 +591,21 @@ func TestAutoApproveRoutes(t *testing.T) {
} }
trx := adb.DB.Save(&node) trx := adb.DB.Save(&node)
assert.NoError(t, trx.Error) require.NoError(t, trx.Error)
sendUpdate, err := adb.SaveNodeRoutes(&node) sendUpdate, err := adb.SaveNodeRoutes(&node)
assert.NoError(t, err) require.NoError(t, err)
assert.False(t, sendUpdate) assert.False(t, sendUpdate)
node0ByID, err := adb.GetNodeByID(0) node0ByID, err := adb.GetNodeByID(0)
assert.NoError(t, err) require.NoError(t, err)
// TODO(kradalby): Check state update // TODO(kradalby): Check state update
err = adb.EnableAutoApprovedRoutes(pol, node0ByID) err = adb.EnableAutoApprovedRoutes(pol, node0ByID)
assert.NoError(t, err) require.NoError(t, err)
enabledRoutes, err := adb.GetEnabledRoutes(node0ByID) enabledRoutes, err := adb.GetEnabledRoutes(node0ByID)
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, enabledRoutes, len(tt.want)) assert.Len(t, enabledRoutes, len(tt.want))
tsaddr.SortPrefixes(enabledRoutes) tsaddr.SortPrefixes(enabledRoutes)
@ -697,13 +698,13 @@ func TestListEphemeralNodes(t *testing.T) {
} }
user, err := db.CreateUser("test") user, err := db.CreateUser("test")
assert.NoError(t, err) require.NoError(t, err)
pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil) pak, err := db.CreatePreAuthKey(user.Name, false, false, nil, nil)
assert.NoError(t, err) require.NoError(t, err)
pakEph, err := db.CreatePreAuthKey(user.Name, false, true, nil, nil) pakEph, err := db.CreatePreAuthKey(user.Name, false, true, nil, nil)
assert.NoError(t, err) require.NoError(t, err)
node := types.Node{ node := types.Node{
ID: 0, ID: 0,
@ -726,16 +727,16 @@ func TestListEphemeralNodes(t *testing.T) {
} }
err = db.DB.Save(&node).Error err = db.DB.Save(&node).Error
assert.NoError(t, err) require.NoError(t, err)
err = db.DB.Save(&nodeEph).Error err = db.DB.Save(&nodeEph).Error
assert.NoError(t, err) require.NoError(t, err)
nodes, err := db.ListNodes() nodes, err := db.ListNodes()
assert.NoError(t, err) require.NoError(t, err)
ephemeralNodes, err := db.ListEphemeralNodes() ephemeralNodes, err := db.ListEphemeralNodes()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, nodes, 2) assert.Len(t, nodes, 2)
assert.Len(t, ephemeralNodes, 1) assert.Len(t, ephemeralNodes, 1)
@ -753,10 +754,10 @@ func TestRenameNode(t *testing.T) {
} }
user, err := db.CreateUser("test") user, err := db.CreateUser("test")
assert.NoError(t, err) require.NoError(t, err)
user2, err := db.CreateUser("test2") user2, err := db.CreateUser("test2")
assert.NoError(t, err) require.NoError(t, err)
node := types.Node{ node := types.Node{
ID: 0, ID: 0,
@ -777,10 +778,10 @@ func TestRenameNode(t *testing.T) {
} }
err = db.DB.Save(&node).Error err = db.DB.Save(&node).Error
assert.NoError(t, err) require.NoError(t, err)
err = db.DB.Save(&node2).Error err = db.DB.Save(&node2).Error
assert.NoError(t, err) require.NoError(t, err)
err = db.DB.Transaction(func(tx *gorm.DB) error { err = db.DB.Transaction(func(tx *gorm.DB) error {
_, err := RegisterNode(tx, node, nil, nil) _, err := RegisterNode(tx, node, nil, nil)
@ -790,10 +791,10 @@ func TestRenameNode(t *testing.T) {
_, err = RegisterNode(tx, node2, nil, nil) _, err = RegisterNode(tx, node2, nil, nil)
return err return err
}) })
assert.NoError(t, err) require.NoError(t, err)
nodes, err := db.ListNodes() nodes, err := db.ListNodes()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, nodes, 2) assert.Len(t, nodes, 2)
@ -815,26 +816,26 @@ func TestRenameNode(t *testing.T) {
err = db.Write(func(tx *gorm.DB) error { err = db.Write(func(tx *gorm.DB) error {
return RenameNode(tx, nodes[0].ID, "newname") return RenameNode(tx, nodes[0].ID, "newname")
}) })
assert.NoError(t, err) require.NoError(t, err)
nodes, err = db.ListNodes() nodes, err = db.ListNodes()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, nodes, 2) assert.Len(t, nodes, 2)
assert.Equal(t, nodes[0].Hostname, "test") assert.Equal(t, "test", nodes[0].Hostname)
assert.Equal(t, nodes[0].GivenName, "newname") assert.Equal(t, "newname", nodes[0].GivenName)
// Nodes can reuse name that is no longer used // Nodes can reuse name that is no longer used
err = db.Write(func(tx *gorm.DB) error { err = db.Write(func(tx *gorm.DB) error {
return RenameNode(tx, nodes[1].ID, "test") return RenameNode(tx, nodes[1].ID, "test")
}) })
assert.NoError(t, err) require.NoError(t, err)
nodes, err = db.ListNodes() nodes, err = db.ListNodes()
assert.NoError(t, err) require.NoError(t, err)
assert.Len(t, nodes, 2) assert.Len(t, nodes, 2)
assert.Equal(t, nodes[0].Hostname, "test") assert.Equal(t, "test", nodes[0].Hostname)
assert.Equal(t, nodes[0].GivenName, "newname") assert.Equal(t, "newname", nodes[0].GivenName)
assert.Equal(t, nodes[1].GivenName, "test") assert.Equal(t, "test", nodes[1].GivenName)
// Nodes cannot be renamed to used names // Nodes cannot be renamed to used names
err = db.Write(func(tx *gorm.DB) error { err = db.Write(func(tx *gorm.DB) error {

View file

@ -648,8 +648,13 @@ func EnableAutoApprovedRoutes(
if approvedAlias == node.User.Username() { if approvedAlias == node.User.Username() {
approvedRoutes = append(approvedRoutes, advertisedRoute) approvedRoutes = append(approvedRoutes, advertisedRoute)
} else { } else {
users, err := ListUsers(tx)
if err != nil {
return fmt.Errorf("looking up users to expand route alias: %w", err)
}
// TODO(kradalby): figure out how to get this to depend on less stuff // TODO(kradalby): figure out how to get this to depend on less stuff
approvedIps, err := aclPolicy.ExpandAlias(types.Nodes{node}, approvedAlias) approvedIps, err := aclPolicy.ExpandAlias(types.Nodes{node}, users, approvedAlias)
if err != nil { if err != nil {
return fmt.Errorf("expanding alias %q for autoApprovers: %w", approvedAlias, err) return fmt.Errorf("expanding alias %q for autoApprovers: %w", approvedAlias, err)
} }

View file

@ -737,14 +737,18 @@ func (api headscaleV1APIServer) SetPolicy(
if err != nil { if err != nil {
return nil, fmt.Errorf("loading nodes from database to validate policy: %w", err) return nil, fmt.Errorf("loading nodes from database to validate policy: %w", err)
} }
users, err := api.h.db.ListUsers()
if err != nil {
return nil, fmt.Errorf("loading users from database to validate policy: %w", err)
}
_, err = pol.CompileFilterRules(nodes) _, err = pol.CompileFilterRules(users, nodes)
if err != nil { if err != nil {
return nil, fmt.Errorf("verifying policy rules: %w", err) return nil, fmt.Errorf("verifying policy rules: %w", err)
} }
if len(nodes) > 0 { if len(nodes) > 0 {
_, err = pol.CompileSSHPolicy(nodes[0], nodes) _, err = pol.CompileSSHPolicy(nodes[0], users, nodes)
if err != nil { if err != nil {
return nil, fmt.Errorf("verifying SSH rules: %w", err) return nil, fmt.Errorf("verifying SSH rules: %w", err)
} }

View file

@ -153,6 +153,7 @@ func addNextDNSMetadata(resolvers []*dnstype.Resolver, node *types.Node) {
func (m *Mapper) fullMapResponse( func (m *Mapper) fullMapResponse(
node *types.Node, node *types.Node,
peers types.Nodes, peers types.Nodes,
users []types.User,
pol *policy.ACLPolicy, pol *policy.ACLPolicy,
capVer tailcfg.CapabilityVersion, capVer tailcfg.CapabilityVersion,
) (*tailcfg.MapResponse, error) { ) (*tailcfg.MapResponse, error) {
@ -167,6 +168,7 @@ func (m *Mapper) fullMapResponse(
pol, pol,
node, node,
capVer, capVer,
users,
peers, peers,
peers, peers,
m.cfg, m.cfg,
@ -189,8 +191,12 @@ func (m *Mapper) FullMapResponse(
if err != nil { if err != nil {
return nil, err return nil, err
} }
users, err := m.db.ListUsers()
if err != nil {
return nil, err
}
resp, err := m.fullMapResponse(node, peers, pol, mapRequest.Version) resp, err := m.fullMapResponse(node, peers, users, pol, mapRequest.Version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -253,6 +259,11 @@ func (m *Mapper) PeerChangedResponse(
return nil, err return nil, err
} }
users, err := m.db.ListUsers()
if err != nil {
return nil, fmt.Errorf("listing users for map response: %w", err)
}
var removedIDs []tailcfg.NodeID var removedIDs []tailcfg.NodeID
var changedIDs []types.NodeID var changedIDs []types.NodeID
for nodeID, nodeChanged := range changed { for nodeID, nodeChanged := range changed {
@ -276,6 +287,7 @@ func (m *Mapper) PeerChangedResponse(
pol, pol,
node, node,
mapRequest.Version, mapRequest.Version,
users,
peers, peers,
changedNodes, changedNodes,
m.cfg, m.cfg,
@ -508,16 +520,17 @@ func appendPeerChanges(
pol *policy.ACLPolicy, pol *policy.ACLPolicy,
node *types.Node, node *types.Node,
capVer tailcfg.CapabilityVersion, capVer tailcfg.CapabilityVersion,
users []types.User,
peers types.Nodes, peers types.Nodes,
changed types.Nodes, changed types.Nodes,
cfg *types.Config, cfg *types.Config,
) error { ) error {
packetFilter, err := pol.CompileFilterRules(append(peers, node)) packetFilter, err := pol.CompileFilterRules(users, append(peers, node))
if err != nil { if err != nil {
return err return err
} }
sshPolicy, err := pol.CompileSSHPolicy(node, peers) sshPolicy, err := pol.CompileSSHPolicy(node, users, peers)
if err != nil { if err != nil {
return err return err
} }

View file

@ -159,6 +159,9 @@ func Test_fullMapResponse(t *testing.T) {
lastSeen := time.Date(2009, time.November, 10, 23, 9, 0, 0, time.UTC) lastSeen := time.Date(2009, time.November, 10, 23, 9, 0, 0, time.UTC)
expire := time.Date(2500, time.November, 11, 23, 0, 0, 0, time.UTC) expire := time.Date(2500, time.November, 11, 23, 0, 0, 0, time.UTC)
user1 := types.User{Model: gorm.Model{ID: 0}, Name: "mini"}
user2 := types.User{Model: gorm.Model{ID: 1}, Name: "peer2"}
mini := &types.Node{ mini := &types.Node{
ID: 0, ID: 0,
MachineKey: mustMK( MachineKey: mustMK(
@ -173,8 +176,8 @@ func Test_fullMapResponse(t *testing.T) {
IPv4: iap("100.64.0.1"), IPv4: iap("100.64.0.1"),
Hostname: "mini", Hostname: "mini",
GivenName: "mini", GivenName: "mini",
UserID: 0, UserID: user1.ID,
User: types.User{Name: "mini"}, User: user1,
ForcedTags: []string{}, ForcedTags: []string{},
AuthKey: &types.PreAuthKey{}, AuthKey: &types.PreAuthKey{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
@ -253,8 +256,8 @@ func Test_fullMapResponse(t *testing.T) {
IPv4: iap("100.64.0.2"), IPv4: iap("100.64.0.2"),
Hostname: "peer1", Hostname: "peer1",
GivenName: "peer1", GivenName: "peer1",
UserID: 0, UserID: user1.ID,
User: types.User{Name: "mini"}, User: user1,
ForcedTags: []string{}, ForcedTags: []string{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
Expiry: &expire, Expiry: &expire,
@ -308,8 +311,8 @@ func Test_fullMapResponse(t *testing.T) {
IPv4: iap("100.64.0.3"), IPv4: iap("100.64.0.3"),
Hostname: "peer2", Hostname: "peer2",
GivenName: "peer2", GivenName: "peer2",
UserID: 1, UserID: user2.ID,
User: types.User{Name: "peer2"}, User: user2,
ForcedTags: []string{}, ForcedTags: []string{},
LastSeen: &lastSeen, LastSeen: &lastSeen,
Expiry: &expire, Expiry: &expire,
@ -468,6 +471,7 @@ func Test_fullMapResponse(t *testing.T) {
got, err := mappy.fullMapResponse( got, err := mappy.fullMapResponse(
tt.node, tt.node,
tt.peers, tt.peers,
[]types.User{user1, user2},
tt.pol, tt.pol,
0, 0,
) )

View file

@ -488,7 +488,7 @@ func (a *AuthProviderOIDC) registerNode(
} }
// TODO(kradalby): // TODO(kradalby):
// Rewrite in elem-go // Rewrite in elem-go.
func renderOIDCCallbackTemplate( func renderOIDCCallbackTemplate(
user *types.User, user *types.User,
) (*bytes.Buffer, error) { ) (*bytes.Buffer, error) {

View file

@ -137,20 +137,21 @@ func GenerateFilterAndSSHRulesForTests(
policy *ACLPolicy, policy *ACLPolicy,
node *types.Node, node *types.Node,
peers types.Nodes, peers types.Nodes,
users []types.User,
) ([]tailcfg.FilterRule, *tailcfg.SSHPolicy, error) { ) ([]tailcfg.FilterRule, *tailcfg.SSHPolicy, error) {
// If there is no policy defined, we default to allow all // If there is no policy defined, we default to allow all
if policy == nil { if policy == nil {
return tailcfg.FilterAllowAll, &tailcfg.SSHPolicy{}, nil return tailcfg.FilterAllowAll, &tailcfg.SSHPolicy{}, nil
} }
rules, err := policy.CompileFilterRules(append(peers, node)) rules, err := policy.CompileFilterRules(users, append(peers, node))
if err != nil { if err != nil {
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
} }
log.Trace().Interface("ACL", rules).Str("node", node.GivenName).Msg("ACL rules") log.Trace().Interface("ACL", rules).Str("node", node.GivenName).Msg("ACL rules")
sshPolicy, err := policy.CompileSSHPolicy(node, peers) sshPolicy, err := policy.CompileSSHPolicy(node, users, peers)
if err != nil { if err != nil {
return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err return []tailcfg.FilterRule{}, &tailcfg.SSHPolicy{}, err
} }
@ -161,6 +162,7 @@ func GenerateFilterAndSSHRulesForTests(
// CompileFilterRules takes a set of nodes and an ACLPolicy and generates a // CompileFilterRules takes a set of nodes and an ACLPolicy and generates a
// set of Tailscale compatible FilterRules used to allow traffic on clients. // set of Tailscale compatible FilterRules used to allow traffic on clients.
func (pol *ACLPolicy) CompileFilterRules( func (pol *ACLPolicy) CompileFilterRules(
users []types.User,
nodes types.Nodes, nodes types.Nodes,
) ([]tailcfg.FilterRule, error) { ) ([]tailcfg.FilterRule, error) {
if pol == nil { if pol == nil {
@ -176,9 +178,14 @@ func (pol *ACLPolicy) CompileFilterRules(
var srcIPs []string var srcIPs []string
for srcIndex, src := range acl.Sources { for srcIndex, src := range acl.Sources {
srcs, err := pol.expandSource(src, nodes) srcs, err := pol.expandSource(src, users, nodes)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing policy, acl index: %d->%d: %w", index, srcIndex, err) return nil, fmt.Errorf(
"parsing policy, acl index: %d->%d: %w",
index,
srcIndex,
err,
)
} }
srcIPs = append(srcIPs, srcs...) srcIPs = append(srcIPs, srcs...)
} }
@ -197,6 +204,7 @@ func (pol *ACLPolicy) CompileFilterRules(
expanded, err := pol.ExpandAlias( expanded, err := pol.ExpandAlias(
nodes, nodes,
users,
alias, alias,
) )
if err != nil { if err != nil {
@ -281,6 +289,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F
func (pol *ACLPolicy) CompileSSHPolicy( func (pol *ACLPolicy) CompileSSHPolicy(
node *types.Node, node *types.Node,
users []types.User,
peers types.Nodes, peers types.Nodes,
) (*tailcfg.SSHPolicy, error) { ) (*tailcfg.SSHPolicy, error) {
if pol == nil { if pol == nil {
@ -312,7 +321,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
for index, sshACL := range pol.SSHs { for index, sshACL := range pol.SSHs {
var dest netipx.IPSetBuilder var dest netipx.IPSetBuilder
for _, src := range sshACL.Destinations { for _, src := range sshACL.Destinations {
expanded, err := pol.ExpandAlias(append(peers, node), src) expanded, err := pol.ExpandAlias(append(peers, node), users, src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -335,12 +344,21 @@ func (pol *ACLPolicy) CompileSSHPolicy(
case "check": case "check":
checkAction, err := sshCheckAction(sshACL.CheckPeriod) checkAction, err := sshCheckAction(sshACL.CheckPeriod)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing SSH policy, parsing check duration, index: %d: %w", index, err) return nil, fmt.Errorf(
"parsing SSH policy, parsing check duration, index: %d: %w",
index,
err,
)
} else { } else {
action = *checkAction action = *checkAction
} }
default: default:
return nil, fmt.Errorf("parsing SSH policy, unknown action %q, index: %d: %w", sshACL.Action, index, err) return nil, fmt.Errorf(
"parsing SSH policy, unknown action %q, index: %d: %w",
sshACL.Action,
index,
err,
)
} }
principals := make([]*tailcfg.SSHPrincipal, 0, len(sshACL.Sources)) principals := make([]*tailcfg.SSHPrincipal, 0, len(sshACL.Sources))
@ -363,6 +381,7 @@ func (pol *ACLPolicy) CompileSSHPolicy(
} else { } else {
expandedSrcs, err := pol.ExpandAlias( expandedSrcs, err := pol.ExpandAlias(
peers, peers,
users,
rawSrc, rawSrc,
) )
if err != nil { if err != nil {
@ -512,9 +531,10 @@ func parseProtocol(protocol string) ([]int, bool, error) {
// with the given src alias. // with the given src alias.
func (pol *ACLPolicy) expandSource( func (pol *ACLPolicy) expandSource(
src string, src string,
users []types.User,
nodes types.Nodes, nodes types.Nodes,
) ([]string, error) { ) ([]string, error) {
ipSet, err := pol.ExpandAlias(nodes, src) ipSet, err := pol.ExpandAlias(nodes, users, src)
if err != nil { if err != nil {
return []string{}, err return []string{}, err
} }
@ -538,6 +558,7 @@ func (pol *ACLPolicy) expandSource(
// and transform these in IPAddresses. // and transform these in IPAddresses.
func (pol *ACLPolicy) ExpandAlias( func (pol *ACLPolicy) ExpandAlias(
nodes types.Nodes, nodes types.Nodes,
users []types.User,
alias string, alias string,
) (*netipx.IPSet, error) { ) (*netipx.IPSet, error) {
if isWildcard(alias) { if isWildcard(alias) {
@ -552,12 +573,12 @@ func (pol *ACLPolicy) ExpandAlias(
// if alias is a group // if alias is a group
if isGroup(alias) { if isGroup(alias) {
return pol.expandIPsFromGroup(alias, nodes) return pol.expandIPsFromGroup(alias, users, nodes)
} }
// if alias is a tag // if alias is a tag
if isTag(alias) { if isTag(alias) {
return pol.expandIPsFromTag(alias, nodes) return pol.expandIPsFromTag(alias, users, nodes)
} }
if isAutoGroup(alias) { if isAutoGroup(alias) {
@ -565,7 +586,7 @@ func (pol *ACLPolicy) ExpandAlias(
} }
// if alias is a user // if alias is a user
if ips, err := pol.expandIPsFromUser(alias, nodes); ips != nil { if ips, err := pol.expandIPsFromUser(alias, users, nodes); ips != nil {
return ips, err return ips, err
} }
@ -574,7 +595,7 @@ func (pol *ACLPolicy) ExpandAlias(
if h, ok := pol.Hosts[alias]; ok { if h, ok := pol.Hosts[alias]; ok {
log.Trace().Str("host", h.String()).Msg("ExpandAlias got hosts entry") log.Trace().Str("host", h.String()).Msg("ExpandAlias got hosts entry")
return pol.ExpandAlias(nodes, h.String()) return pol.ExpandAlias(nodes, users, h.String())
} }
// if alias is an IP // if alias is an IP
@ -599,7 +620,7 @@ func (pol *ACLPolicy) ExpandAlias(
// TODO(kradalby): It is quite hard to understand what this function is doing, // TODO(kradalby): It is quite hard to understand what this function is doing,
// it seems like it trying to ensure that we dont include nodes that are tagged // it seems like it trying to ensure that we dont include nodes that are tagged
// when we look up the nodes owned by a user. // when we look up the nodes owned by a user.
// This should be refactored to be more clear as part of the Tags work in #1369 // This should be refactored to be more clear as part of the Tags work in #1369.
func excludeCorrectlyTaggedNodes( func excludeCorrectlyTaggedNodes(
aclPolicy *ACLPolicy, aclPolicy *ACLPolicy,
nodes types.Nodes, nodes types.Nodes,
@ -751,16 +772,17 @@ func (pol *ACLPolicy) expandUsersFromGroup(
func (pol *ACLPolicy) expandIPsFromGroup( func (pol *ACLPolicy) expandIPsFromGroup(
group string, group string,
users []types.User,
nodes types.Nodes, nodes types.Nodes,
) (*netipx.IPSet, error) { ) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder var build netipx.IPSetBuilder
users, err := pol.expandUsersFromGroup(group) userTokens, err := pol.expandUsersFromGroup(group)
if err != nil { if err != nil {
return &netipx.IPSet{}, err return &netipx.IPSet{}, err
} }
for _, user := range users { for _, user := range userTokens {
filteredNodes := filterNodesByUser(nodes, user) filteredNodes := filterNodesByUser(nodes, users, user)
for _, node := range filteredNodes { for _, node := range filteredNodes {
node.AppendToIPSet(&build) node.AppendToIPSet(&build)
} }
@ -771,6 +793,7 @@ func (pol *ACLPolicy) expandIPsFromGroup(
func (pol *ACLPolicy) expandIPsFromTag( func (pol *ACLPolicy) expandIPsFromTag(
alias string, alias string,
users []types.User,
nodes types.Nodes, nodes types.Nodes,
) (*netipx.IPSet, error) { ) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder var build netipx.IPSetBuilder
@ -803,7 +826,7 @@ func (pol *ACLPolicy) expandIPsFromTag(
// filter out nodes per tag owner // filter out nodes per tag owner
for _, user := range owners { for _, user := range owners {
nodes := filterNodesByUser(nodes, user) nodes := filterNodesByUser(nodes, users, user)
for _, node := range nodes { for _, node := range nodes {
if node.Hostinfo == nil { if node.Hostinfo == nil {
continue continue
@ -820,11 +843,12 @@ func (pol *ACLPolicy) expandIPsFromTag(
func (pol *ACLPolicy) expandIPsFromUser( func (pol *ACLPolicy) expandIPsFromUser(
user string, user string,
users []types.User,
nodes types.Nodes, nodes types.Nodes,
) (*netipx.IPSet, error) { ) (*netipx.IPSet, error) {
var build netipx.IPSetBuilder var build netipx.IPSetBuilder
filteredNodes := filterNodesByUser(nodes, user) filteredNodes := filterNodesByUser(nodes, users, user)
filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user) filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user)
// shortcurcuit if we have no nodes to get ips from. // shortcurcuit if we have no nodes to get ips from.
@ -953,10 +977,43 @@ func (pol *ACLPolicy) TagsOfNode(
return validTags, invalidTags return validTags, invalidTags
} }
func filterNodesByUser(nodes types.Nodes, user string) types.Nodes { // filterNodesByUser returns a list of nodes that match the given userToken from a
// policy.
// Matching nodes are determined by first matching the user token to a user by checking:
// - If it is an ID that mactches the user database ID
// - It is the Provider Identifier from OIDC
// - It matches the username or email of a user
//
// If the token matches more than one user, zero nodes will returned.
func filterNodesByUser(nodes types.Nodes, users []types.User, userToken string) types.Nodes {
var out types.Nodes var out types.Nodes
var potentialUsers []types.User
for _, user := range users {
if user.ProviderIdentifier.Valid && user.ProviderIdentifier.String == userToken {
// If a user is matching with a known unique field,
// disgard all other users and only keep the current
// user.
potentialUsers = []types.User{user}
break
}
if user.Email == userToken {
potentialUsers = append(potentialUsers, user)
}
if user.Name == userToken {
potentialUsers = append(potentialUsers, user)
}
}
if len(potentialUsers) != 1 {
return nil
}
user := potentialUsers[0]
for _, node := range nodes { for _, node := range nodes {
if node.User.Username() == user { if node.User.ID == user.ID {
out = append(out, node) out = append(out, node)
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ func Windows(url string) *elem.Element {
elem.Text("headscale - Windows"), elem.Text("headscale - Windows"),
), ),
elem.Body(attrs.Props{ elem.Body(attrs.Props{
attrs.Style : bodyStyle.ToInline(), attrs.Style: bodyStyle.ToInline(),
}, },
headerOne("headscale: Windows configuration"), headerOne("headscale: Windows configuration"),
elem.P(nil, elem.P(nil,
@ -21,7 +21,8 @@ func Windows(url string) *elem.Element {
elem.A(attrs.Props{ elem.A(attrs.Props{
attrs.Href: "https://tailscale.com/download/windows", attrs.Href: "https://tailscale.com/download/windows",
attrs.Rel: "noreferrer noopener", attrs.Rel: "noreferrer noopener",
attrs.Target: "_blank"}, attrs.Target: "_blank",
},
elem.Text("Tailscale for Windows ")), elem.Text("Tailscale for Windows ")),
elem.Text("and install it."), elem.Text("and install it."),
), ),

View file

@ -9,6 +9,7 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/dnstype" "tailscale.com/types/dnstype"
) )
@ -36,8 +37,17 @@ func TestReadConfig(t *testing.T) {
MagicDNS: true, MagicDNS: true,
BaseDomain: "example.com", BaseDomain: "example.com",
Nameservers: Nameservers{ Nameservers: Nameservers{
Global: []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", "https://dns.nextdns.io/abc123"}, Global: []string{
Split: map[string][]string{"darp.headscale.net": {"1.1.1.1", "8.8.8.8"}, "foo.bar.com": {"1.1.1.1"}}, "1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
"https://dns.nextdns.io/abc123",
},
Split: map[string][]string{
"darp.headscale.net": {"1.1.1.1", "8.8.8.8"},
"foo.bar.com": {"1.1.1.1"},
},
}, },
ExtraRecords: []tailcfg.DNSRecord{ ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"}, {Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
@ -92,8 +102,17 @@ func TestReadConfig(t *testing.T) {
MagicDNS: false, MagicDNS: false,
BaseDomain: "example.com", BaseDomain: "example.com",
Nameservers: Nameservers{ Nameservers: Nameservers{
Global: []string{"1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", "https://dns.nextdns.io/abc123"}, Global: []string{
Split: map[string][]string{"darp.headscale.net": {"1.1.1.1", "8.8.8.8"}, "foo.bar.com": {"1.1.1.1"}}, "1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
"https://dns.nextdns.io/abc123",
},
Split: map[string][]string{
"darp.headscale.net": {"1.1.1.1", "8.8.8.8"},
"foo.bar.com": {"1.1.1.1"},
},
}, },
ExtraRecords: []tailcfg.DNSRecord{ ExtraRecords: []tailcfg.DNSRecord{
{Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"}, {Name: "grafana.myvpn.example.com", Type: "A", Value: "100.64.0.3"},
@ -187,7 +206,7 @@ func TestReadConfig(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
viper.Reset() viper.Reset()
err := LoadConfig(tt.configPath, true) err := LoadConfig(tt.configPath, true)
assert.NoError(t, err) require.NoError(t, err)
conf, err := tt.setup(t) conf, err := tt.setup(t)
@ -197,7 +216,7 @@ func TestReadConfig(t *testing.T) {
return return
} }
assert.NoError(t, err) require.NoError(t, err)
if diff := cmp.Diff(tt.want, conf); diff != "" { if diff := cmp.Diff(tt.want, conf); diff != "" {
t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff) t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff)
@ -277,10 +296,10 @@ func TestReadConfigFromEnv(t *testing.T) {
viper.Reset() viper.Reset()
err := LoadConfig("testdata/minimal.yaml", true) err := LoadConfig("testdata/minimal.yaml", true)
assert.NoError(t, err) require.NoError(t, err)
conf, err := tt.setup(t) conf, err := tt.setup(t)
assert.NoError(t, err) require.NoError(t, err)
if diff := cmp.Diff(tt.want, conf); diff != "" { if diff := cmp.Diff(tt.want, conf); diff != "" {
t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff) t.Errorf("ReadConfig() mismatch (-want +got):\n%s", diff)
@ -311,13 +330,25 @@ noise:
// Check configuration validation errors (1) // Check configuration validation errors (1)
err = LoadConfig(tmpDir, false) err = LoadConfig(tmpDir, false)
assert.NoError(t, err) require.NoError(t, err)
err = validateServerConfig() err = validateServerConfig()
assert.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both") assert.Contains(
assert.Contains(t, err.Error(), "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are") t,
assert.Contains(t, err.Error(), "Fatal config error: server_url must start with https:// or http://") err.Error(),
"Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both",
)
assert.Contains(
t,
err.Error(),
"Fatal config error: the only supported values for tls_letsencrypt_challenge_type are",
)
assert.Contains(
t,
err.Error(),
"Fatal config error: server_url must start with https:// or http://",
)
// Check configuration validation errors (2) // Check configuration validation errors (2)
configYaml = []byte(`--- configYaml = []byte(`---
@ -332,7 +363,7 @@ tls_letsencrypt_challenge_type: TLS-ALPN-01
t.Fatalf("Couldn't write file %s", configFilePath) t.Fatalf("Couldn't write file %s", configFilePath)
} }
err = LoadConfig(tmpDir, false) err = LoadConfig(tmpDir, false)
assert.NoError(t, err) require.NoError(t, err)
} }
// OK // OK

View file

@ -2,6 +2,8 @@ package types
import ( import (
"cmp" "cmp"
"database/sql"
"net/mail"
"strconv" "strconv"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1" v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
@ -34,7 +36,7 @@ type User struct {
// Unique identifier of the user from OIDC, // Unique identifier of the user from OIDC,
// comes from `sub` claim in the OIDC token // comes from `sub` claim in the OIDC token
// and is used to lookup the user. // and is used to lookup the user.
ProviderIdentifier string `gorm:"index"` ProviderIdentifier sql.NullString `gorm:"index"`
// Provider is the origin of the user account, // Provider is the origin of the user account,
// same as RegistrationMethod, without authkey. // same as RegistrationMethod, without authkey.
@ -51,7 +53,7 @@ type User struct {
// should be used throughout headscale, in information returned to the // should be used throughout headscale, in information returned to the
// user and the Policy engine. // user and the Policy engine.
func (u *User) Username() string { func (u *User) Username() string {
return cmp.Or(u.Email, u.Name, u.ProviderIdentifier, strconv.FormatUint(uint64(u.ID), 10)) return cmp.Or(u.Email, u.Name, u.ProviderIdentifier.String, strconv.FormatUint(uint64(u.ID), 10))
} }
// DisplayNameOrUsername returns the DisplayName if it exists, otherwise // DisplayNameOrUsername returns the DisplayName if it exists, otherwise
@ -107,7 +109,7 @@ func (u *User) Proto() *v1.User {
CreatedAt: timestamppb.New(u.CreatedAt), CreatedAt: timestamppb.New(u.CreatedAt),
DisplayName: u.DisplayName, DisplayName: u.DisplayName,
Email: u.Email, Email: u.Email,
ProviderId: u.ProviderIdentifier, ProviderId: u.ProviderIdentifier.String,
Provider: u.Provider, Provider: u.Provider,
ProfilePicUrl: u.ProfilePicURL, ProfilePicUrl: u.ProfilePicURL,
} }
@ -129,10 +131,20 @@ type OIDCClaims struct {
// FromClaim overrides a User from OIDC claims. // FromClaim overrides a User from OIDC claims.
// All fields will be updated, except for the ID. // All fields will be updated, except for the ID.
func (u *User) FromClaim(claims *OIDCClaims) { func (u *User) FromClaim(claims *OIDCClaims) {
u.ProviderIdentifier = claims.Sub err := util.CheckForFQDNRules(claims.Username)
if err == nil {
u.Name = claims.Username
}
if claims.EmailVerified {
_, err = mail.ParseAddress(claims.Email)
if err == nil {
u.Email = claims.Email
}
}
u.ProviderIdentifier.String = claims.Sub
u.DisplayName = claims.Name u.DisplayName = claims.Name
u.Email = claims.Email
u.Name = claims.Username
u.ProfilePicURL = claims.ProfilePictureURL u.ProfilePicURL = claims.ProfilePictureURL
u.Provider = util.RegisterMethodOIDC u.Provider = util.RegisterMethodOIDC
} }

View file

@ -4,12 +4,13 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestGenerateRandomStringDNSSafe(t *testing.T) { func TestGenerateRandomStringDNSSafe(t *testing.T) {
for i := 0; i < 100000; i++ { for i := 0; i < 100000; i++ {
str, err := GenerateRandomStringDNSSafe(8) str, err := GenerateRandomStringDNSSafe(8)
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, str, 8) assert.Len(t, str, 8)
} }
} }

View file

@ -12,6 +12,7 @@ import (
"github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic" "github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var veryLargeDestination = []string{ var veryLargeDestination = []string{
@ -54,7 +55,7 @@ func aclScenario(
) *Scenario { ) *Scenario {
t.Helper() t.Helper()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
spec := map[string]int{ spec := map[string]int{
"user1": clientsPerUser, "user1": clientsPerUser,
@ -77,10 +78,10 @@ func aclScenario(
hsic.WithACLPolicy(policy), hsic.WithACLPolicy(policy),
hsic.WithTestName("acl"), hsic.WithTestName("acl"),
) )
assertNoErr(t, err) require.NoError(t, err)
_, err = scenario.ListTailscaleClientsFQDNs() _, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err) require.NoError(t, err)
return scenario return scenario
} }
@ -267,7 +268,7 @@ func TestACLHostsInNetMapTable(t *testing.T) {
for name, testCase := range tests { for name, testCase := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
spec := testCase.users spec := testCase.users
@ -275,22 +276,22 @@ func TestACLHostsInNetMapTable(t *testing.T) {
[]tsic.Option{}, []tsic.Option{},
hsic.WithACLPolicy(&testCase.policy), hsic.WithACLPolicy(&testCase.policy),
) )
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
allClients, err := scenario.ListTailscaleClients() allClients, err := scenario.ListTailscaleClients()
assertNoErr(t, err) require.NoError(t, err)
err = scenario.WaitForTailscaleSyncWithPeerCount(testCase.want["user1"]) err = scenario.WaitForTailscaleSyncWithPeerCount(testCase.want["user1"])
assertNoErrSync(t, err) require.NoError(t, err)
for _, client := range allClients { for _, client := range allClients {
status, err := client.Status() status, err := client.Status()
assertNoErr(t, err) require.NoError(t, err)
user := status.User[status.Self.UserID].LoginName user := status.User[status.Self.UserID].LoginName
assert.Equal(t, (testCase.want[user]), len(status.Peer)) assert.Len(t, status.Peer, (testCase.want[user]))
} }
}) })
} }
@ -319,23 +320,23 @@ func TestACLAllowUser80Dst(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
user1Clients, err := scenario.ListTailscaleClients("user1") user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err) require.NoError(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2") user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err) require.NoError(t, err)
// Test that user1 can visit all user2 // Test that user1 can visit all user2
for _, client := range user1Clients { for _, client := range user1Clients {
for _, peer := range user2Clients { for _, peer := range user2Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
@ -343,14 +344,14 @@ func TestACLAllowUser80Dst(t *testing.T) {
for _, client := range user2Clients { for _, client := range user2Clients {
for _, peer := range user1Clients { for _, peer := range user1Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
} }
} }
} }
@ -376,10 +377,10 @@ func TestACLDenyAllPort80(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
allClients, err := scenario.ListTailscaleClients() allClients, err := scenario.ListTailscaleClients()
assertNoErr(t, err) require.NoError(t, err)
allHostnames, err := scenario.ListTailscaleClientsFQDNs() allHostnames, err := scenario.ListTailscaleClientsFQDNs()
assertNoErr(t, err) require.NoError(t, err)
for _, client := range allClients { for _, client := range allClients {
for _, hostname := range allHostnames { for _, hostname := range allHostnames {
@ -394,7 +395,7 @@ func TestACLDenyAllPort80(t *testing.T) {
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
} }
} }
} }
@ -420,23 +421,23 @@ func TestACLAllowUserDst(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
user1Clients, err := scenario.ListTailscaleClients("user1") user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err) require.NoError(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2") user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err) require.NoError(t, err)
// Test that user1 can visit all user2 // Test that user1 can visit all user2
for _, client := range user1Clients { for _, client := range user1Clients {
for _, peer := range user2Clients { for _, peer := range user2Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
@ -444,14 +445,14 @@ func TestACLAllowUserDst(t *testing.T) {
for _, client := range user2Clients { for _, client := range user2Clients {
for _, peer := range user1Clients { for _, peer := range user1Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
} }
} }
} }
@ -476,23 +477,23 @@ func TestACLAllowStarDst(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
user1Clients, err := scenario.ListTailscaleClients("user1") user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err) require.NoError(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2") user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err) require.NoError(t, err)
// Test that user1 can visit all user2 // Test that user1 can visit all user2
for _, client := range user1Clients { for _, client := range user1Clients {
for _, peer := range user2Clients { for _, peer := range user2Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
@ -500,14 +501,14 @@ func TestACLAllowStarDst(t *testing.T) {
for _, client := range user2Clients { for _, client := range user2Clients {
for _, peer := range user1Clients { for _, peer := range user1Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
} }
} }
} }
@ -537,23 +538,23 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
user1Clients, err := scenario.ListTailscaleClients("user1") user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err) require.NoError(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2") user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err) require.NoError(t, err)
// Test that user1 can visit all user2 // Test that user1 can visit all user2
for _, client := range user1Clients { for _, client := range user1Clients {
for _, peer := range user2Clients { for _, peer := range user2Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
@ -561,14 +562,14 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) {
for _, client := range user2Clients { for _, client := range user2Clients {
for _, peer := range user1Clients { for _, peer := range user1Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
} }
@ -679,10 +680,10 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test1ip4 := netip.MustParseAddr("100.64.0.1") test1ip4 := netip.MustParseAddr("100.64.0.1")
test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1") test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1")
test1, err := scenario.FindTailscaleClientByIP(test1ip6) test1, err := scenario.FindTailscaleClientByIP(test1ip6)
assertNoErr(t, err) require.NoError(t, err)
test1fqdn, err := test1.FQDN() test1fqdn, err := test1.FQDN()
assertNoErr(t, err) require.NoError(t, err)
test1ip4URL := fmt.Sprintf("http://%s/etc/hostname", test1ip4.String()) test1ip4URL := fmt.Sprintf("http://%s/etc/hostname", test1ip4.String())
test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String()) test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String())
test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn) test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn)
@ -690,10 +691,10 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test2ip4 := netip.MustParseAddr("100.64.0.2") test2ip4 := netip.MustParseAddr("100.64.0.2")
test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2") test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2")
test2, err := scenario.FindTailscaleClientByIP(test2ip6) test2, err := scenario.FindTailscaleClientByIP(test2ip6)
assertNoErr(t, err) require.NoError(t, err)
test2fqdn, err := test2.FQDN() test2fqdn, err := test2.FQDN()
assertNoErr(t, err) require.NoError(t, err)
test2ip4URL := fmt.Sprintf("http://%s/etc/hostname", test2ip4.String()) test2ip4URL := fmt.Sprintf("http://%s/etc/hostname", test2ip4.String())
test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String()) test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String())
test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn) test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn)
@ -701,10 +702,10 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3ip4 := netip.MustParseAddr("100.64.0.3") test3ip4 := netip.MustParseAddr("100.64.0.3")
test3ip6 := netip.MustParseAddr("fd7a:115c:a1e0::3") test3ip6 := netip.MustParseAddr("fd7a:115c:a1e0::3")
test3, err := scenario.FindTailscaleClientByIP(test3ip6) test3, err := scenario.FindTailscaleClientByIP(test3ip6)
assertNoErr(t, err) require.NoError(t, err)
test3fqdn, err := test3.FQDN() test3fqdn, err := test3.FQDN()
assertNoErr(t, err) require.NoError(t, err)
test3ip4URL := fmt.Sprintf("http://%s/etc/hostname", test3ip4.String()) test3ip4URL := fmt.Sprintf("http://%s/etc/hostname", test3ip4.String())
test3ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test3ip6.String()) test3ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test3ip6.String())
test3fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test3fqdn) test3fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test3fqdn)
@ -719,7 +720,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3ip4URL, test3ip4URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test3ip6URL) result, err = test1.Curl(test3ip6URL)
assert.Lenf( assert.Lenf(
@ -730,7 +731,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3ip6URL, test3ip6URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test3fqdnURL) result, err = test1.Curl(test3fqdnURL)
assert.Lenf( assert.Lenf(
@ -741,7 +742,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3fqdnURL, test3fqdnURL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
// test2 can query test3 // test2 can query test3
result, err = test2.Curl(test3ip4URL) result, err = test2.Curl(test3ip4URL)
@ -753,7 +754,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3ip4URL, test3ip4URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test2.Curl(test3ip6URL) result, err = test2.Curl(test3ip6URL)
assert.Lenf( assert.Lenf(
@ -764,7 +765,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3ip6URL, test3ip6URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test2.Curl(test3fqdnURL) result, err = test2.Curl(test3fqdnURL)
assert.Lenf( assert.Lenf(
@ -775,33 +776,33 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test3fqdnURL, test3fqdnURL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
// test3 cannot query test1 // test3 cannot query test1
result, err = test3.Curl(test1ip4URL) result, err = test3.Curl(test1ip4URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test3.Curl(test1ip6URL) result, err = test3.Curl(test1ip6URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test3.Curl(test1fqdnURL) result, err = test3.Curl(test1fqdnURL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
// test3 cannot query test2 // test3 cannot query test2
result, err = test3.Curl(test2ip4URL) result, err = test3.Curl(test2ip4URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test3.Curl(test2ip6URL) result, err = test3.Curl(test2ip6URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test3.Curl(test2fqdnURL) result, err = test3.Curl(test2fqdnURL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
// test1 can query test2 // test1 can query test2
result, err = test1.Curl(test2ip4URL) result, err = test1.Curl(test2ip4URL)
@ -814,7 +815,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test2ip6URL) result, err = test1.Curl(test2ip6URL)
assert.Lenf( assert.Lenf(
t, t,
@ -824,7 +825,7 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test2ip6URL, test2ip6URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test2fqdnURL) result, err = test1.Curl(test2fqdnURL)
assert.Lenf( assert.Lenf(
@ -835,20 +836,20 @@ func TestACLNamedHostsCanReach(t *testing.T) {
test2fqdnURL, test2fqdnURL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
// test2 cannot query test1 // test2 cannot query test1
result, err = test2.Curl(test1ip4URL) result, err = test2.Curl(test1ip4URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test2.Curl(test1ip6URL) result, err = test2.Curl(test1ip6URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test2.Curl(test1fqdnURL) result, err = test2.Curl(test1fqdnURL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
}) })
} }
} }
@ -946,10 +947,10 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1") test1ip6 := netip.MustParseAddr("fd7a:115c:a1e0::1")
test1, err := scenario.FindTailscaleClientByIP(test1ip) test1, err := scenario.FindTailscaleClientByIP(test1ip)
assert.NotNil(t, test1) assert.NotNil(t, test1)
assertNoErr(t, err) require.NoError(t, err)
test1fqdn, err := test1.FQDN() test1fqdn, err := test1.FQDN()
assertNoErr(t, err) require.NoError(t, err)
test1ipURL := fmt.Sprintf("http://%s/etc/hostname", test1ip.String()) test1ipURL := fmt.Sprintf("http://%s/etc/hostname", test1ip.String())
test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String()) test1ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test1ip6.String())
test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn) test1fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test1fqdn)
@ -958,10 +959,10 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2") test2ip6 := netip.MustParseAddr("fd7a:115c:a1e0::2")
test2, err := scenario.FindTailscaleClientByIP(test2ip) test2, err := scenario.FindTailscaleClientByIP(test2ip)
assert.NotNil(t, test2) assert.NotNil(t, test2)
assertNoErr(t, err) require.NoError(t, err)
test2fqdn, err := test2.FQDN() test2fqdn, err := test2.FQDN()
assertNoErr(t, err) require.NoError(t, err)
test2ipURL := fmt.Sprintf("http://%s/etc/hostname", test2ip.String()) test2ipURL := fmt.Sprintf("http://%s/etc/hostname", test2ip.String())
test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String()) test2ip6URL := fmt.Sprintf("http://[%s]/etc/hostname", test2ip6.String())
test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn) test2fqdnURL := fmt.Sprintf("http://%s/etc/hostname", test2fqdn)
@ -976,7 +977,7 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
test2ipURL, test2ipURL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test2ip6URL) result, err = test1.Curl(test2ip6URL)
assert.Lenf( assert.Lenf(
@ -987,7 +988,7 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
test2ip6URL, test2ip6URL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test1.Curl(test2fqdnURL) result, err = test1.Curl(test2fqdnURL)
assert.Lenf( assert.Lenf(
@ -998,19 +999,19 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) {
test2fqdnURL, test2fqdnURL,
result, result,
) )
assertNoErr(t, err) require.NoError(t, err)
result, err = test2.Curl(test1ipURL) result, err = test2.Curl(test1ipURL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test2.Curl(test1ip6URL) result, err = test2.Curl(test1ip6URL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
result, err = test2.Curl(test1fqdnURL) result, err = test2.Curl(test1fqdnURL)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
}) })
} }
} }
@ -1020,7 +1021,7 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1046,19 +1047,19 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
"HEADSCALE_POLICY_MODE": "database", "HEADSCALE_POLICY_MODE": "database",
}), }),
) )
assertNoErr(t, err) require.NoError(t, err)
_, err = scenario.ListTailscaleClientsFQDNs() _, err = scenario.ListTailscaleClientsFQDNs()
assertNoErrListFQDN(t, err) require.NoError(t, err)
err = scenario.WaitForTailscaleSync() err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err) require.NoError(t, err)
user1Clients, err := scenario.ListTailscaleClients("user1") user1Clients, err := scenario.ListTailscaleClients("user1")
assertNoErr(t, err) require.NoError(t, err)
user2Clients, err := scenario.ListTailscaleClients("user2") user2Clients, err := scenario.ListTailscaleClients("user2")
assertNoErr(t, err) require.NoError(t, err)
all := append(user1Clients, user2Clients...) all := append(user1Clients, user2Clients...)
@ -1070,19 +1071,19 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
} }
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
p := policy.ACLPolicy{ p := policy.ACLPolicy{
ACLs: []policy.ACL{ ACLs: []policy.ACL{
@ -1100,7 +1101,7 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
policyFilePath := "/etc/headscale/policy.json" policyFilePath := "/etc/headscale/policy.json"
err = headscale.WriteFile(policyFilePath, pBytes) err = headscale.WriteFile(policyFilePath, pBytes)
assertNoErr(t, err) require.NoError(t, err)
// No policy is present at this time. // No policy is present at this time.
// Add a new policy from a file. // Add a new policy from a file.
@ -1113,7 +1114,7 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
policyFilePath, policyFilePath,
}, },
) )
assertNoErr(t, err) require.NoError(t, err)
// Get the current policy and check // Get the current policy and check
// if it is the same as the one we set. // if it is the same as the one we set.
@ -1129,7 +1130,7 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
}, },
&output, &output,
) )
assertNoErr(t, err) require.NoError(t, err)
assert.Len(t, output.ACLs, 1) assert.Len(t, output.ACLs, 1)
@ -1141,14 +1142,14 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
for _, client := range user1Clients { for _, client := range user1Clients {
for _, peer := range user2Clients { for _, peer := range user2Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Len(t, result, 13) assert.Len(t, result, 13)
assertNoErr(t, err) require.NoError(t, err)
} }
} }
@ -1156,14 +1157,14 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) {
for _, client := range user2Clients { for _, client := range user2Clients {
for _, peer := range user1Clients { for _, peer := range user1Clients {
fqdn, err := peer.FQDN() fqdn, err := peer.FQDN()
assertNoErr(t, err) require.NoError(t, err)
url := fmt.Sprintf("http://%s/etc/hostname", fqdn) url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url) t.Logf("url from %s to %s", client.Hostname(), url)
result, err := client.Curl(url) result, err := client.Curl(url)
assert.Empty(t, result) assert.Empty(t, result)
assert.Error(t, err) require.Error(t, err)
} }
} }
} }

View file

@ -13,6 +13,7 @@ import (
"github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic" "github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func executeAndUnmarshal[T any](headscale ControlServer, command []string, result T) error { func executeAndUnmarshal[T any](headscale ControlServer, command []string, result T) error {
@ -34,7 +35,7 @@ func TestUserCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -43,10 +44,10 @@ func TestUserCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
var listUsers []v1.User var listUsers []v1.User
err = executeAndUnmarshal(headscale, err = executeAndUnmarshal(headscale,
@ -59,7 +60,7 @@ func TestUserCommand(t *testing.T) {
}, },
&listUsers, &listUsers,
) )
assertNoErr(t, err) require.NoError(t, err)
result := []string{listUsers[0].GetName(), listUsers[1].GetName()} result := []string{listUsers[0].GetName(), listUsers[1].GetName()}
sort.Strings(result) sort.Strings(result)
@ -81,7 +82,7 @@ func TestUserCommand(t *testing.T) {
"newname", "newname",
}, },
) )
assertNoErr(t, err) require.NoError(t, err)
var listAfterRenameUsers []v1.User var listAfterRenameUsers []v1.User
err = executeAndUnmarshal(headscale, err = executeAndUnmarshal(headscale,
@ -94,7 +95,7 @@ func TestUserCommand(t *testing.T) {
}, },
&listAfterRenameUsers, &listAfterRenameUsers,
) )
assertNoErr(t, err) require.NoError(t, err)
result = []string{listAfterRenameUsers[0].GetName(), listAfterRenameUsers[1].GetName()} result = []string{listAfterRenameUsers[0].GetName(), listAfterRenameUsers[1].GetName()}
sort.Strings(result) sort.Strings(result)
@ -114,7 +115,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
count := 3 count := 3
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -122,13 +123,13 @@ func TestPreAuthKeyCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
keys := make([]*v1.PreAuthKey, count) keys := make([]*v1.PreAuthKey, count)
assertNoErr(t, err) require.NoError(t, err)
for index := 0; index < count; index++ { for index := 0; index < count; index++ {
var preAuthKey v1.PreAuthKey var preAuthKey v1.PreAuthKey
@ -150,7 +151,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
}, },
&preAuthKey, &preAuthKey,
) )
assertNoErr(t, err) require.NoError(t, err)
keys[index] = &preAuthKey keys[index] = &preAuthKey
} }
@ -171,7 +172,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
}, },
&listedPreAuthKeys, &listedPreAuthKeys,
) )
assertNoErr(t, err) require.NoError(t, err)
// There is one key created by "scenario.CreateHeadscaleEnv" // There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 4) assert.Len(t, listedPreAuthKeys, 4)
@ -212,7 +213,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
continue continue
} }
assert.Equal(t, listedPreAuthKeys[index].GetAclTags(), []string{"tag:test1", "tag:test2"}) assert.Equal(t, []string{"tag:test1", "tag:test2"}, listedPreAuthKeys[index].GetAclTags())
} }
// Test key expiry // Test key expiry
@ -226,7 +227,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
listedPreAuthKeys[1].GetKey(), listedPreAuthKeys[1].GetKey(),
}, },
) )
assertNoErr(t, err) require.NoError(t, err)
var listedPreAuthKeysAfterExpire []v1.PreAuthKey var listedPreAuthKeysAfterExpire []v1.PreAuthKey
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -242,7 +243,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
}, },
&listedPreAuthKeysAfterExpire, &listedPreAuthKeysAfterExpire,
) )
assertNoErr(t, err) require.NoError(t, err)
assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().AsTime().Before(time.Now())) assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().AsTime().Before(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().AsTime().After(time.Now())) assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().AsTime().After(time.Now()))
@ -256,7 +257,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
user := "pre-auth-key-without-exp-user" user := "pre-auth-key-without-exp-user"
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -264,10 +265,10 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipaknaexp")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipaknaexp"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
var preAuthKey v1.PreAuthKey var preAuthKey v1.PreAuthKey
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -284,7 +285,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
}, },
&preAuthKey, &preAuthKey,
) )
assertNoErr(t, err) require.NoError(t, err)
var listedPreAuthKeys []v1.PreAuthKey var listedPreAuthKeys []v1.PreAuthKey
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -300,7 +301,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
}, },
&listedPreAuthKeys, &listedPreAuthKeys,
) )
assertNoErr(t, err) require.NoError(t, err)
// There is one key created by "scenario.CreateHeadscaleEnv" // There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 2) assert.Len(t, listedPreAuthKeys, 2)
@ -319,7 +320,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
user := "pre-auth-key-reus-ephm-user" user := "pre-auth-key-reus-ephm-user"
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -327,10 +328,10 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipakresueeph")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipakresueeph"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
var preAuthReusableKey v1.PreAuthKey var preAuthReusableKey v1.PreAuthKey
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -347,7 +348,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
}, },
&preAuthReusableKey, &preAuthReusableKey,
) )
assertNoErr(t, err) require.NoError(t, err)
var preAuthEphemeralKey v1.PreAuthKey var preAuthEphemeralKey v1.PreAuthKey
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -364,7 +365,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
}, },
&preAuthEphemeralKey, &preAuthEphemeralKey,
) )
assertNoErr(t, err) require.NoError(t, err)
assert.True(t, preAuthEphemeralKey.GetEphemeral()) assert.True(t, preAuthEphemeralKey.GetEphemeral())
assert.False(t, preAuthEphemeralKey.GetReusable()) assert.False(t, preAuthEphemeralKey.GetReusable())
@ -383,7 +384,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
}, },
&listedPreAuthKeys, &listedPreAuthKeys,
) )
assertNoErr(t, err) require.NoError(t, err)
// There is one key created by "scenario.CreateHeadscaleEnv" // There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 3) assert.Len(t, listedPreAuthKeys, 3)
@ -397,7 +398,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
user2 := "user2" user2 := "user2"
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -413,10 +414,10 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
hsic.WithTLS(), hsic.WithTLS(),
hsic.WithHostnameAsServerURL(), hsic.WithHostnameAsServerURL(),
) )
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
var user2Key v1.PreAuthKey var user2Key v1.PreAuthKey
@ -438,10 +439,10 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
}, },
&user2Key, &user2Key,
) )
assertNoErr(t, err) require.NoError(t, err)
allClients, err := scenario.ListTailscaleClients() allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err) require.NoError(t, err)
assert.Len(t, allClients, 1) assert.Len(t, allClients, 1)
@ -449,22 +450,22 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
// Log out from user1 // Log out from user1
err = client.Logout() err = client.Logout()
assertNoErr(t, err) require.NoError(t, err)
err = scenario.WaitForTailscaleLogout() err = scenario.WaitForTailscaleLogout()
assertNoErr(t, err) require.NoError(t, err)
status, err := client.Status() status, err := client.Status()
assertNoErr(t, err) require.NoError(t, err)
if status.BackendState == "Starting" || status.BackendState == "Running" { if status.BackendState == "Starting" || status.BackendState == "Running" {
t.Fatalf("expected node to be logged out, backend state: %s", status.BackendState) t.Fatalf("expected node to be logged out, backend state: %s", status.BackendState)
} }
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey()) err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
assertNoErr(t, err) require.NoError(t, err)
status, err = client.Status() status, err = client.Status()
assertNoErr(t, err) require.NoError(t, err)
if status.BackendState != "Running" { if status.BackendState != "Running" {
t.Fatalf("expected node to be logged in, backend state: %s", status.BackendState) t.Fatalf("expected node to be logged in, backend state: %s", status.BackendState)
} }
@ -485,7 +486,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
}, },
&listNodes, &listNodes,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listNodes, 1) assert.Len(t, listNodes, 1)
assert.Equal(t, "user2", listNodes[0].GetUser().GetName()) assert.Equal(t, "user2", listNodes[0].GetUser().GetName())
@ -498,7 +499,7 @@ func TestApiKeyCommand(t *testing.T) {
count := 5 count := 5
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -507,10 +508,10 @@ func TestApiKeyCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
keys := make([]string, count) keys := make([]string, count)
@ -526,7 +527,7 @@ func TestApiKeyCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
assert.NotEmpty(t, apiResult) assert.NotEmpty(t, apiResult)
keys[idx] = apiResult keys[idx] = apiResult
@ -545,7 +546,7 @@ func TestApiKeyCommand(t *testing.T) {
}, },
&listedAPIKeys, &listedAPIKeys,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listedAPIKeys, 5) assert.Len(t, listedAPIKeys, 5)
@ -601,7 +602,7 @@ func TestApiKeyCommand(t *testing.T) {
listedAPIKeys[idx].GetPrefix(), listedAPIKeys[idx].GetPrefix(),
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
expiredPrefixes[listedAPIKeys[idx].GetPrefix()] = true expiredPrefixes[listedAPIKeys[idx].GetPrefix()] = true
} }
@ -617,7 +618,7 @@ func TestApiKeyCommand(t *testing.T) {
}, },
&listedAfterExpireAPIKeys, &listedAfterExpireAPIKeys,
) )
assert.Nil(t, err) require.NoError(t, err)
for index := range listedAfterExpireAPIKeys { for index := range listedAfterExpireAPIKeys {
if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].GetPrefix()]; ok { if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].GetPrefix()]; ok {
@ -643,7 +644,7 @@ func TestApiKeyCommand(t *testing.T) {
"--prefix", "--prefix",
listedAPIKeys[0].GetPrefix(), listedAPIKeys[0].GetPrefix(),
}) })
assert.Nil(t, err) require.NoError(t, err)
var listedAPIKeysAfterDelete []v1.ApiKey var listedAPIKeysAfterDelete []v1.ApiKey
err = executeAndUnmarshal(headscale, err = executeAndUnmarshal(headscale,
@ -656,7 +657,7 @@ func TestApiKeyCommand(t *testing.T) {
}, },
&listedAPIKeysAfterDelete, &listedAPIKeysAfterDelete,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listedAPIKeysAfterDelete, 4) assert.Len(t, listedAPIKeysAfterDelete, 4)
} }
@ -666,7 +667,7 @@ func TestNodeTagCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -674,17 +675,17 @@ func TestNodeTagCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
machineKeys := []string{ machineKeys := []string{
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", "mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
"mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", "mkey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c",
} }
nodes := make([]*v1.Node, len(machineKeys)) nodes := make([]*v1.Node, len(machineKeys))
assert.Nil(t, err) require.NoError(t, err)
for index, machineKey := range machineKeys { for index, machineKey := range machineKeys {
_, err := headscale.Execute( _, err := headscale.Execute(
@ -702,7 +703,7 @@ func TestNodeTagCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -720,7 +721,7 @@ func TestNodeTagCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
nodes[index] = &node nodes[index] = &node
} }
@ -739,7 +740,7 @@ func TestNodeTagCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, []string{"tag:test"}, node.GetForcedTags()) assert.Equal(t, []string{"tag:test"}, node.GetForcedTags())
@ -753,7 +754,7 @@ func TestNodeTagCommand(t *testing.T) {
"--output", "json", "--output", "json",
}, },
) )
assert.ErrorContains(t, err, "tag must start with the string 'tag:'") require.ErrorContains(t, err, "tag must start with the string 'tag:'")
// Test list all nodes after added seconds // Test list all nodes after added seconds
resultMachines := make([]*v1.Node, len(machineKeys)) resultMachines := make([]*v1.Node, len(machineKeys))
@ -767,7 +768,7 @@ func TestNodeTagCommand(t *testing.T) {
}, },
&resultMachines, &resultMachines,
) )
assert.Nil(t, err) require.NoError(t, err)
found := false found := false
for _, node := range resultMachines { for _, node := range resultMachines {
if node.GetForcedTags() != nil { if node.GetForcedTags() != nil {
@ -778,9 +779,8 @@ func TestNodeTagCommand(t *testing.T) {
} }
} }
} }
assert.Equal( assert.True(
t, t,
true,
found, found,
"should find a node with the tag 'tag:test' in the list of nodes", "should find a node with the tag 'tag:test' in the list of nodes",
) )
@ -791,18 +791,22 @@ func TestNodeAdvertiseTagNoACLCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
"user1": 1, "user1": 1,
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithTags([]string{"tag:test"})}, hsic.WithTestName("cliadvtags")) err = scenario.CreateHeadscaleEnv(
assertNoErr(t, err) spec,
[]tsic.Option{tsic.WithTags([]string{"tag:test"})},
hsic.WithTestName("cliadvtags"),
)
require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Test list all nodes after added seconds // Test list all nodes after added seconds
resultMachines := make([]*v1.Node, spec["user1"]) resultMachines := make([]*v1.Node, spec["user1"])
@ -817,7 +821,7 @@ func TestNodeAdvertiseTagNoACLCommand(t *testing.T) {
}, },
&resultMachines, &resultMachines,
) )
assert.Nil(t, err) require.NoError(t, err)
found := false found := false
for _, node := range resultMachines { for _, node := range resultMachines {
if node.GetInvalidTags() != nil { if node.GetInvalidTags() != nil {
@ -828,9 +832,8 @@ func TestNodeAdvertiseTagNoACLCommand(t *testing.T) {
} }
} }
} }
assert.Equal( assert.True(
t, t,
true,
found, found,
"should not find a node with the tag 'tag:test' in the list of nodes", "should not find a node with the tag 'tag:test' in the list of nodes",
) )
@ -841,31 +844,36 @@ func TestNodeAdvertiseTagWithACLCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
"user1": 1, "user1": 1,
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithTags([]string{"tag:exists"})}, hsic.WithTestName("cliadvtags"), hsic.WithACLPolicy( err = scenario.CreateHeadscaleEnv(
&policy.ACLPolicy{ spec,
ACLs: []policy.ACL{ []tsic.Option{tsic.WithTags([]string{"tag:exists"})},
{ hsic.WithTestName("cliadvtags"),
Action: "accept", hsic.WithACLPolicy(
Sources: []string{"*"}, &policy.ACLPolicy{
Destinations: []string{"*:*"}, ACLs: []policy.ACL{
{
Action: "accept",
Sources: []string{"*"},
Destinations: []string{"*:*"},
},
},
TagOwners: map[string][]string{
"tag:exists": {"user1"},
}, },
}, },
TagOwners: map[string][]string{ ),
"tag:exists": {"user1"}, )
}, require.NoError(t, err)
},
))
assertNoErr(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Test list all nodes after added seconds // Test list all nodes after added seconds
resultMachines := make([]*v1.Node, spec["user1"]) resultMachines := make([]*v1.Node, spec["user1"])
@ -880,7 +888,7 @@ func TestNodeAdvertiseTagWithACLCommand(t *testing.T) {
}, },
&resultMachines, &resultMachines,
) )
assert.Nil(t, err) require.NoError(t, err)
found := false found := false
for _, node := range resultMachines { for _, node := range resultMachines {
if node.GetValidTags() != nil { if node.GetValidTags() != nil {
@ -891,9 +899,8 @@ func TestNodeAdvertiseTagWithACLCommand(t *testing.T) {
} }
} }
} }
assert.Equal( assert.True(
t, t,
true,
found, found,
"should not find a node with the tag 'tag:exists' in the list of nodes", "should not find a node with the tag 'tag:exists' in the list of nodes",
) )
@ -904,7 +911,7 @@ func TestNodeCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -913,10 +920,10 @@ func TestNodeCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Pregenerated machine keys // Pregenerated machine keys
machineKeys := []string{ machineKeys := []string{
@ -927,7 +934,7 @@ func TestNodeCommand(t *testing.T) {
"mkey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", "mkey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084",
} }
nodes := make([]*v1.Node, len(machineKeys)) nodes := make([]*v1.Node, len(machineKeys))
assert.Nil(t, err) require.NoError(t, err)
for index, machineKey := range machineKeys { for index, machineKey := range machineKeys {
_, err := headscale.Execute( _, err := headscale.Execute(
@ -945,7 +952,7 @@ func TestNodeCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -963,7 +970,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
nodes[index] = &node nodes[index] = &node
} }
@ -983,7 +990,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&listAll, &listAll,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAll, 5) assert.Len(t, listAll, 5)
@ -1004,7 +1011,7 @@ func TestNodeCommand(t *testing.T) {
"mkey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584", "mkey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584",
} }
otherUserMachines := make([]*v1.Node, len(otherUserMachineKeys)) otherUserMachines := make([]*v1.Node, len(otherUserMachineKeys))
assert.Nil(t, err) require.NoError(t, err)
for index, machineKey := range otherUserMachineKeys { for index, machineKey := range otherUserMachineKeys {
_, err := headscale.Execute( _, err := headscale.Execute(
@ -1022,7 +1029,7 @@ func TestNodeCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1040,7 +1047,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
otherUserMachines[index] = &node otherUserMachines[index] = &node
} }
@ -1060,7 +1067,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&listAllWithotherUser, &listAllWithotherUser,
) )
assert.Nil(t, err) require.NoError(t, err)
// All nodes, nodes + otherUser // All nodes, nodes + otherUser
assert.Len(t, listAllWithotherUser, 7) assert.Len(t, listAllWithotherUser, 7)
@ -1086,7 +1093,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&listOnlyotherUserMachineUser, &listOnlyotherUserMachineUser,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listOnlyotherUserMachineUser, 2) assert.Len(t, listOnlyotherUserMachineUser, 2)
@ -1118,7 +1125,7 @@ func TestNodeCommand(t *testing.T) {
"--force", "--force",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
// Test: list main user after node is deleted // Test: list main user after node is deleted
var listOnlyMachineUserAfterDelete []v1.Node var listOnlyMachineUserAfterDelete []v1.Node
@ -1135,7 +1142,7 @@ func TestNodeCommand(t *testing.T) {
}, },
&listOnlyMachineUserAfterDelete, &listOnlyMachineUserAfterDelete,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listOnlyMachineUserAfterDelete, 4) assert.Len(t, listOnlyMachineUserAfterDelete, 4)
} }
@ -1145,7 +1152,7 @@ func TestNodeExpireCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1153,10 +1160,10 @@ func TestNodeExpireCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Pregenerated machine keys // Pregenerated machine keys
machineKeys := []string{ machineKeys := []string{
@ -1184,7 +1191,7 @@ func TestNodeExpireCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1202,7 +1209,7 @@ func TestNodeExpireCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
nodes[index] = &node nodes[index] = &node
} }
@ -1221,7 +1228,7 @@ func TestNodeExpireCommand(t *testing.T) {
}, },
&listAll, &listAll,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAll, 5) assert.Len(t, listAll, 5)
@ -1241,7 +1248,7 @@ func TestNodeExpireCommand(t *testing.T) {
fmt.Sprintf("%d", listAll[idx].GetId()), fmt.Sprintf("%d", listAll[idx].GetId()),
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
} }
var listAllAfterExpiry []v1.Node var listAllAfterExpiry []v1.Node
@ -1256,7 +1263,7 @@ func TestNodeExpireCommand(t *testing.T) {
}, },
&listAllAfterExpiry, &listAllAfterExpiry,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAllAfterExpiry, 5) assert.Len(t, listAllAfterExpiry, 5)
@ -1272,7 +1279,7 @@ func TestNodeRenameCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1280,10 +1287,10 @@ func TestNodeRenameCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Pregenerated machine keys // Pregenerated machine keys
machineKeys := []string{ machineKeys := []string{
@ -1294,7 +1301,7 @@ func TestNodeRenameCommand(t *testing.T) {
"mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", "mkey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe",
} }
nodes := make([]*v1.Node, len(machineKeys)) nodes := make([]*v1.Node, len(machineKeys))
assert.Nil(t, err) require.NoError(t, err)
for index, machineKey := range machineKeys { for index, machineKey := range machineKeys {
_, err := headscale.Execute( _, err := headscale.Execute(
@ -1312,7 +1319,7 @@ func TestNodeRenameCommand(t *testing.T) {
"json", "json",
}, },
) )
assertNoErr(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1330,7 +1337,7 @@ func TestNodeRenameCommand(t *testing.T) {
}, },
&node, &node,
) )
assertNoErr(t, err) require.NoError(t, err)
nodes[index] = &node nodes[index] = &node
} }
@ -1349,7 +1356,7 @@ func TestNodeRenameCommand(t *testing.T) {
}, },
&listAll, &listAll,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAll, 5) assert.Len(t, listAll, 5)
@ -1370,7 +1377,7 @@ func TestNodeRenameCommand(t *testing.T) {
fmt.Sprintf("newnode-%d", idx+1), fmt.Sprintf("newnode-%d", idx+1),
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Contains(t, res, "Node renamed") assert.Contains(t, res, "Node renamed")
} }
@ -1387,7 +1394,7 @@ func TestNodeRenameCommand(t *testing.T) {
}, },
&listAllAfterRename, &listAllAfterRename,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAllAfterRename, 5) assert.Len(t, listAllAfterRename, 5)
@ -1408,7 +1415,7 @@ func TestNodeRenameCommand(t *testing.T) {
strings.Repeat("t", 64), strings.Repeat("t", 64),
}, },
) )
assert.ErrorContains(t, err, "not be over 63 chars") require.ErrorContains(t, err, "not be over 63 chars")
var listAllAfterRenameAttempt []v1.Node var listAllAfterRenameAttempt []v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1422,7 +1429,7 @@ func TestNodeRenameCommand(t *testing.T) {
}, },
&listAllAfterRenameAttempt, &listAllAfterRenameAttempt,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, listAllAfterRenameAttempt, 5) assert.Len(t, listAllAfterRenameAttempt, 5)
@ -1438,7 +1445,7 @@ func TestNodeMoveCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1447,10 +1454,10 @@ func TestNodeMoveCommand(t *testing.T) {
} }
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins"))
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
// Randomly generated node key // Randomly generated node key
machineKey := "mkey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa" machineKey := "mkey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa"
@ -1470,7 +1477,7 @@ func TestNodeMoveCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
var node v1.Node var node v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1488,11 +1495,11 @@ func TestNodeMoveCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, uint64(1), node.GetId()) assert.Equal(t, uint64(1), node.GetId())
assert.Equal(t, "nomad-node", node.GetName()) assert.Equal(t, "nomad-node", node.GetName())
assert.Equal(t, node.GetUser().GetName(), "old-user") assert.Equal(t, "old-user", node.GetUser().GetName())
nodeID := fmt.Sprintf("%d", node.GetId()) nodeID := fmt.Sprintf("%d", node.GetId())
@ -1511,9 +1518,9 @@ func TestNodeMoveCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, node.GetUser().GetName(), "new-user") assert.Equal(t, "new-user", node.GetUser().GetName())
var allNodes []v1.Node var allNodes []v1.Node
err = executeAndUnmarshal( err = executeAndUnmarshal(
@ -1527,13 +1534,13 @@ func TestNodeMoveCommand(t *testing.T) {
}, },
&allNodes, &allNodes,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, allNodes, 1) assert.Len(t, allNodes, 1)
assert.Equal(t, allNodes[0].GetId(), node.GetId()) assert.Equal(t, allNodes[0].GetId(), node.GetId())
assert.Equal(t, allNodes[0].GetUser(), node.GetUser()) assert.Equal(t, allNodes[0].GetUser(), node.GetUser())
assert.Equal(t, allNodes[0].GetUser().GetName(), "new-user") assert.Equal(t, "new-user", allNodes[0].GetUser().GetName())
_, err = headscale.Execute( _, err = headscale.Execute(
[]string{ []string{
@ -1548,12 +1555,12 @@ func TestNodeMoveCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.ErrorContains( require.ErrorContains(
t, t,
err, err,
"user not found", "user not found",
) )
assert.Equal(t, node.GetUser().GetName(), "new-user") assert.Equal(t, "new-user", node.GetUser().GetName())
err = executeAndUnmarshal( err = executeAndUnmarshal(
headscale, headscale,
@ -1570,9 +1577,9 @@ func TestNodeMoveCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, node.GetUser().GetName(), "old-user") assert.Equal(t, "old-user", node.GetUser().GetName())
err = executeAndUnmarshal( err = executeAndUnmarshal(
headscale, headscale,
@ -1589,9 +1596,9 @@ func TestNodeMoveCommand(t *testing.T) {
}, },
&node, &node,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Equal(t, node.GetUser().GetName(), "old-user") assert.Equal(t, "old-user", node.GetUser().GetName())
} }
func TestPolicyCommand(t *testing.T) { func TestPolicyCommand(t *testing.T) {
@ -1599,7 +1606,7 @@ func TestPolicyCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1614,10 +1621,10 @@ func TestPolicyCommand(t *testing.T) {
"HEADSCALE_POLICY_MODE": "database", "HEADSCALE_POLICY_MODE": "database",
}), }),
) )
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
p := policy.ACLPolicy{ p := policy.ACLPolicy{
ACLs: []policy.ACL{ ACLs: []policy.ACL{
@ -1637,7 +1644,7 @@ func TestPolicyCommand(t *testing.T) {
policyFilePath := "/etc/headscale/policy.json" policyFilePath := "/etc/headscale/policy.json"
err = headscale.WriteFile(policyFilePath, pBytes) err = headscale.WriteFile(policyFilePath, pBytes)
assertNoErr(t, err) require.NoError(t, err)
// No policy is present at this time. // No policy is present at this time.
// Add a new policy from a file. // Add a new policy from a file.
@ -1651,7 +1658,7 @@ func TestPolicyCommand(t *testing.T) {
}, },
) )
assertNoErr(t, err) require.NoError(t, err)
// Get the current policy and check // Get the current policy and check
// if it is the same as the one we set. // if it is the same as the one we set.
@ -1667,11 +1674,11 @@ func TestPolicyCommand(t *testing.T) {
}, },
&output, &output,
) )
assertNoErr(t, err) require.NoError(t, err)
assert.Len(t, output.TagOwners, 1) assert.Len(t, output.TagOwners, 1)
assert.Len(t, output.ACLs, 1) assert.Len(t, output.ACLs, 1)
assert.Equal(t, output.TagOwners["tag:exists"], []string{"policy-user"}) assert.Equal(t, []string{"policy-user"}, output.TagOwners["tag:exists"])
} }
func TestPolicyBrokenConfigCommand(t *testing.T) { func TestPolicyBrokenConfigCommand(t *testing.T) {
@ -1679,7 +1686,7 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
t.Parallel() t.Parallel()
scenario, err := NewScenario(dockertestMaxWait()) scenario, err := NewScenario(dockertestMaxWait())
assertNoErr(t, err) require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t) defer scenario.ShutdownAssertNoPanics(t)
spec := map[string]int{ spec := map[string]int{
@ -1694,10 +1701,10 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
"HEADSCALE_POLICY_MODE": "database", "HEADSCALE_POLICY_MODE": "database",
}), }),
) )
assertNoErr(t, err) require.NoError(t, err)
headscale, err := scenario.Headscale() headscale, err := scenario.Headscale()
assertNoErr(t, err) require.NoError(t, err)
p := policy.ACLPolicy{ p := policy.ACLPolicy{
ACLs: []policy.ACL{ ACLs: []policy.ACL{
@ -1719,7 +1726,7 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
policyFilePath := "/etc/headscale/policy.json" policyFilePath := "/etc/headscale/policy.json"
err = headscale.WriteFile(policyFilePath, pBytes) err = headscale.WriteFile(policyFilePath, pBytes)
assertNoErr(t, err) require.NoError(t, err)
// No policy is present at this time. // No policy is present at this time.
// Add a new policy from a file. // Add a new policy from a file.
@ -1732,7 +1739,7 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
policyFilePath, policyFilePath,
}, },
) )
assert.ErrorContains(t, err, "verifying policy rules: invalid action") require.ErrorContains(t, err, "verifying policy rules: invalid action")
// The new policy was invalid, the old one should still be in place, which // The new policy was invalid, the old one should still be in place, which
// is none. // is none.
@ -1745,5 +1752,5 @@ func TestPolicyBrokenConfigCommand(t *testing.T) {
"json", "json",
}, },
) )
assert.ErrorContains(t, err, "acl policy not found") require.ErrorContains(t, err, "acl policy not found")
} }

View file

@ -18,6 +18,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"tailscale.com/client/tailscale/apitype" "tailscale.com/client/tailscale/apitype"
"tailscale.com/types/key" "tailscale.com/types/key"
@ -244,7 +245,11 @@ func TestEphemeral(t *testing.T) {
} }
func TestEphemeralInAlternateTimezone(t *testing.T) { func TestEphemeralInAlternateTimezone(t *testing.T) {
testEphemeralWithOptions(t, hsic.WithTestName("ephemeral-tz"), hsic.WithTimezone("America/Los_Angeles")) testEphemeralWithOptions(
t,
hsic.WithTestName("ephemeral-tz"),
hsic.WithTimezone("America/Los_Angeles"),
)
} }
func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) { func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) {
@ -1164,10 +1169,10 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) {
}, },
&nodeList, &nodeList,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, nodeList, 2) assert.Len(t, nodeList, 2)
assert.True(t, nodeList[0].Online) assert.True(t, nodeList[0].GetOnline())
assert.True(t, nodeList[1].Online) assert.True(t, nodeList[1].GetOnline())
// Delete the first node, which is online // Delete the first node, which is online
_, err = headscale.Execute( _, err = headscale.Execute(
@ -1177,13 +1182,13 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) {
"delete", "delete",
"--identifier", "--identifier",
// Delete the last added machine // Delete the last added machine
fmt.Sprintf("%d", nodeList[0].Id), fmt.Sprintf("%d", nodeList[0].GetId()),
"--output", "--output",
"json", "json",
"--force", "--force",
}, },
) )
assert.Nil(t, err) require.NoError(t, err)
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
@ -1200,9 +1205,8 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) {
}, },
&nodeListAfter, &nodeListAfter,
) )
assert.Nil(t, err) require.NoError(t, err)
assert.Len(t, nodeListAfter, 1) assert.Len(t, nodeListAfter, 1)
assert.True(t, nodeListAfter[0].Online) assert.True(t, nodeListAfter[0].GetOnline())
assert.Equal(t, nodeList[1].Id, nodeListAfter[0].Id) assert.Equal(t, nodeList[1].GetId(), nodeListAfter[0].GetId())
} }

View file

@ -92,9 +92,9 @@ func TestEnablingRoutes(t *testing.T) {
assert.Len(t, routes, 3) assert.Len(t, routes, 3)
for _, route := range routes { for _, route := range routes {
assert.Equal(t, true, route.GetAdvertised()) assert.True(t, route.GetAdvertised())
assert.Equal(t, false, route.GetEnabled()) assert.False(t, route.GetEnabled())
assert.Equal(t, false, route.GetIsPrimary()) assert.False(t, route.GetIsPrimary())
} }
// Verify that no routes has been sent to the client, // Verify that no routes has been sent to the client,
@ -139,9 +139,9 @@ func TestEnablingRoutes(t *testing.T) {
assert.Len(t, enablingRoutes, 3) assert.Len(t, enablingRoutes, 3)
for _, route := range enablingRoutes { for _, route := range enablingRoutes {
assert.Equal(t, true, route.GetAdvertised()) assert.True(t, route.GetAdvertised())
assert.Equal(t, true, route.GetEnabled()) assert.True(t, route.GetEnabled())
assert.Equal(t, true, route.GetIsPrimary()) assert.True(t, route.GetIsPrimary())
} }
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
@ -212,18 +212,18 @@ func TestEnablingRoutes(t *testing.T) {
assertNoErr(t, err) assertNoErr(t, err)
for _, route := range disablingRoutes { for _, route := range disablingRoutes {
assert.Equal(t, true, route.GetAdvertised()) assert.True(t, route.GetAdvertised())
if route.GetId() == routeToBeDisabled.GetId() { if route.GetId() == routeToBeDisabled.GetId() {
assert.Equal(t, false, route.GetEnabled()) assert.False(t, route.GetEnabled())
// since this is the only route of this cidr, // since this is the only route of this cidr,
// it will not failover, and remain Primary // it will not failover, and remain Primary
// until something can replace it. // until something can replace it.
assert.Equal(t, true, route.GetIsPrimary()) assert.True(t, route.GetIsPrimary())
} else { } else {
assert.Equal(t, true, route.GetEnabled()) assert.True(t, route.GetEnabled())
assert.Equal(t, true, route.GetIsPrimary()) assert.True(t, route.GetIsPrimary())
} }
} }
@ -342,9 +342,9 @@ func TestHASubnetRouterFailover(t *testing.T) {
t.Logf("initial routes %#v", routes) t.Logf("initial routes %#v", routes)
for _, route := range routes { for _, route := range routes {
assert.Equal(t, true, route.GetAdvertised()) assert.True(t, route.GetAdvertised())
assert.Equal(t, false, route.GetEnabled()) assert.False(t, route.GetEnabled())
assert.Equal(t, false, route.GetIsPrimary()) assert.False(t, route.GetIsPrimary())
} }
// Verify that no routes has been sent to the client, // Verify that no routes has been sent to the client,
@ -391,14 +391,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, enablingRoutes, 2) assert.Len(t, enablingRoutes, 2)
// Node 1 is primary // Node 1 is primary
assert.Equal(t, true, enablingRoutes[0].GetAdvertised()) assert.True(t, enablingRoutes[0].GetAdvertised())
assert.Equal(t, true, enablingRoutes[0].GetEnabled()) assert.True(t, enablingRoutes[0].GetEnabled())
assert.Equal(t, true, enablingRoutes[0].GetIsPrimary(), "both subnet routers are up, expected r1 to be primary") assert.True(t, enablingRoutes[0].GetIsPrimary(), "both subnet routers are up, expected r1 to be primary")
// Node 2 is not primary // Node 2 is not primary
assert.Equal(t, true, enablingRoutes[1].GetAdvertised()) assert.True(t, enablingRoutes[1].GetAdvertised())
assert.Equal(t, true, enablingRoutes[1].GetEnabled()) assert.True(t, enablingRoutes[1].GetEnabled())
assert.Equal(t, false, enablingRoutes[1].GetIsPrimary(), "both subnet routers are up, expected r2 to be non-primary") assert.False(t, enablingRoutes[1].GetIsPrimary(), "both subnet routers are up, expected r2 to be non-primary")
// Verify that the client has routes from the primary machine // Verify that the client has routes from the primary machine
srs1, err := subRouter1.Status() srs1, err := subRouter1.Status()
@ -446,14 +446,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, routesAfterMove, 2) assert.Len(t, routesAfterMove, 2)
// Node 1 is not primary // Node 1 is not primary
assert.Equal(t, true, routesAfterMove[0].GetAdvertised()) assert.True(t, routesAfterMove[0].GetAdvertised())
assert.Equal(t, true, routesAfterMove[0].GetEnabled()) assert.True(t, routesAfterMove[0].GetEnabled())
assert.Equal(t, false, routesAfterMove[0].GetIsPrimary(), "r1 is down, expected r2 to be primary") assert.False(t, routesAfterMove[0].GetIsPrimary(), "r1 is down, expected r2 to be primary")
// Node 2 is primary // Node 2 is primary
assert.Equal(t, true, routesAfterMove[1].GetAdvertised()) assert.True(t, routesAfterMove[1].GetAdvertised())
assert.Equal(t, true, routesAfterMove[1].GetEnabled()) assert.True(t, routesAfterMove[1].GetEnabled())
assert.Equal(t, true, routesAfterMove[1].GetIsPrimary(), "r1 is down, expected r2 to be primary") assert.True(t, routesAfterMove[1].GetIsPrimary(), "r1 is down, expected r2 to be primary")
srs2, err = subRouter2.Status() srs2, err = subRouter2.Status()
@ -501,16 +501,16 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, routesAfterBothDown, 2) assert.Len(t, routesAfterBothDown, 2)
// Node 1 is not primary // Node 1 is not primary
assert.Equal(t, true, routesAfterBothDown[0].GetAdvertised()) assert.True(t, routesAfterBothDown[0].GetAdvertised())
assert.Equal(t, true, routesAfterBothDown[0].GetEnabled()) assert.True(t, routesAfterBothDown[0].GetEnabled())
assert.Equal(t, false, routesAfterBothDown[0].GetIsPrimary(), "r1 and r2 is down, expected r2 to _still_ be primary") assert.False(t, routesAfterBothDown[0].GetIsPrimary(), "r1 and r2 is down, expected r2 to _still_ be primary")
// Node 2 is primary // Node 2 is primary
// if the node goes down, but no other suitable route is // if the node goes down, but no other suitable route is
// available, keep the last known good route. // available, keep the last known good route.
assert.Equal(t, true, routesAfterBothDown[1].GetAdvertised()) assert.True(t, routesAfterBothDown[1].GetAdvertised())
assert.Equal(t, true, routesAfterBothDown[1].GetEnabled()) assert.True(t, routesAfterBothDown[1].GetEnabled())
assert.Equal(t, true, routesAfterBothDown[1].GetIsPrimary(), "r1 and r2 is down, expected r2 to _still_ be primary") assert.True(t, routesAfterBothDown[1].GetIsPrimary(), "r1 and r2 is down, expected r2 to _still_ be primary")
// TODO(kradalby): Check client status // TODO(kradalby): Check client status
// Both are expected to be down // Both are expected to be down
@ -560,14 +560,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, routesAfter1Up, 2) assert.Len(t, routesAfter1Up, 2)
// Node 1 is primary // Node 1 is primary
assert.Equal(t, true, routesAfter1Up[0].GetAdvertised()) assert.True(t, routesAfter1Up[0].GetAdvertised())
assert.Equal(t, true, routesAfter1Up[0].GetEnabled()) assert.True(t, routesAfter1Up[0].GetEnabled())
assert.Equal(t, true, routesAfter1Up[0].GetIsPrimary(), "r1 is back up, expected r1 to become be primary") assert.True(t, routesAfter1Up[0].GetIsPrimary(), "r1 is back up, expected r1 to become be primary")
// Node 2 is not primary // Node 2 is not primary
assert.Equal(t, true, routesAfter1Up[1].GetAdvertised()) assert.True(t, routesAfter1Up[1].GetAdvertised())
assert.Equal(t, true, routesAfter1Up[1].GetEnabled()) assert.True(t, routesAfter1Up[1].GetEnabled())
assert.Equal(t, false, routesAfter1Up[1].GetIsPrimary(), "r1 is back up, expected r1 to become be primary") assert.False(t, routesAfter1Up[1].GetIsPrimary(), "r1 is back up, expected r1 to become be primary")
// Verify that the route is announced from subnet router 1 // Verify that the route is announced from subnet router 1
clientStatus, err = client.Status() clientStatus, err = client.Status()
@ -614,14 +614,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, routesAfter2Up, 2) assert.Len(t, routesAfter2Up, 2)
// Node 1 is not primary // Node 1 is not primary
assert.Equal(t, true, routesAfter2Up[0].GetAdvertised()) assert.True(t, routesAfter2Up[0].GetAdvertised())
assert.Equal(t, true, routesAfter2Up[0].GetEnabled()) assert.True(t, routesAfter2Up[0].GetEnabled())
assert.Equal(t, true, routesAfter2Up[0].GetIsPrimary(), "r1 and r2 is back up, expected r1 to _still_ be primary") assert.True(t, routesAfter2Up[0].GetIsPrimary(), "r1 and r2 is back up, expected r1 to _still_ be primary")
// Node 2 is primary // Node 2 is primary
assert.Equal(t, true, routesAfter2Up[1].GetAdvertised()) assert.True(t, routesAfter2Up[1].GetAdvertised())
assert.Equal(t, true, routesAfter2Up[1].GetEnabled()) assert.True(t, routesAfter2Up[1].GetEnabled())
assert.Equal(t, false, routesAfter2Up[1].GetIsPrimary(), "r1 and r2 is back up, expected r1 to _still_ be primary") assert.False(t, routesAfter2Up[1].GetIsPrimary(), "r1 and r2 is back up, expected r1 to _still_ be primary")
// Verify that the route is announced from subnet router 1 // Verify that the route is announced from subnet router 1
clientStatus, err = client.Status() clientStatus, err = client.Status()
@ -677,14 +677,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
t.Logf("routes after disabling r1 %#v", routesAfterDisabling1) t.Logf("routes after disabling r1 %#v", routesAfterDisabling1)
// Node 1 is not primary // Node 1 is not primary
assert.Equal(t, true, routesAfterDisabling1[0].GetAdvertised()) assert.True(t, routesAfterDisabling1[0].GetAdvertised())
assert.Equal(t, false, routesAfterDisabling1[0].GetEnabled()) assert.False(t, routesAfterDisabling1[0].GetEnabled())
assert.Equal(t, false, routesAfterDisabling1[0].GetIsPrimary()) assert.False(t, routesAfterDisabling1[0].GetIsPrimary())
// Node 2 is primary // Node 2 is primary
assert.Equal(t, true, routesAfterDisabling1[1].GetAdvertised()) assert.True(t, routesAfterDisabling1[1].GetAdvertised())
assert.Equal(t, true, routesAfterDisabling1[1].GetEnabled()) assert.True(t, routesAfterDisabling1[1].GetEnabled())
assert.Equal(t, true, routesAfterDisabling1[1].GetIsPrimary()) assert.True(t, routesAfterDisabling1[1].GetIsPrimary())
// Verify that the route is announced from subnet router 1 // Verify that the route is announced from subnet router 1
clientStatus, err = client.Status() clientStatus, err = client.Status()
@ -735,14 +735,14 @@ func TestHASubnetRouterFailover(t *testing.T) {
assert.Len(t, routesAfterEnabling1, 2) assert.Len(t, routesAfterEnabling1, 2)
// Node 1 is not primary // Node 1 is not primary
assert.Equal(t, true, routesAfterEnabling1[0].GetAdvertised()) assert.True(t, routesAfterEnabling1[0].GetAdvertised())
assert.Equal(t, true, routesAfterEnabling1[0].GetEnabled()) assert.True(t, routesAfterEnabling1[0].GetEnabled())
assert.Equal(t, false, routesAfterEnabling1[0].GetIsPrimary()) assert.False(t, routesAfterEnabling1[0].GetIsPrimary())
// Node 2 is primary // Node 2 is primary
assert.Equal(t, true, routesAfterEnabling1[1].GetAdvertised()) assert.True(t, routesAfterEnabling1[1].GetAdvertised())
assert.Equal(t, true, routesAfterEnabling1[1].GetEnabled()) assert.True(t, routesAfterEnabling1[1].GetEnabled())
assert.Equal(t, true, routesAfterEnabling1[1].GetIsPrimary()) assert.True(t, routesAfterEnabling1[1].GetIsPrimary())
// Verify that the route is announced from subnet router 1 // Verify that the route is announced from subnet router 1
clientStatus, err = client.Status() clientStatus, err = client.Status()
@ -795,9 +795,9 @@ func TestHASubnetRouterFailover(t *testing.T) {
t.Logf("routes after deleting r2 %#v", routesAfterDeleting2) t.Logf("routes after deleting r2 %#v", routesAfterDeleting2)
// Node 1 is primary // Node 1 is primary
assert.Equal(t, true, routesAfterDeleting2[0].GetAdvertised()) assert.True(t, routesAfterDeleting2[0].GetAdvertised())
assert.Equal(t, true, routesAfterDeleting2[0].GetEnabled()) assert.True(t, routesAfterDeleting2[0].GetEnabled())
assert.Equal(t, true, routesAfterDeleting2[0].GetIsPrimary()) assert.True(t, routesAfterDeleting2[0].GetIsPrimary())
// Verify that the route is announced from subnet router 1 // Verify that the route is announced from subnet router 1
clientStatus, err = client.Status() clientStatus, err = client.Status()
@ -893,9 +893,9 @@ func TestEnableDisableAutoApprovedRoute(t *testing.T) {
assert.Len(t, routes, 1) assert.Len(t, routes, 1)
// All routes should be auto approved and enabled // All routes should be auto approved and enabled
assert.Equal(t, true, routes[0].GetAdvertised()) assert.True(t, routes[0].GetAdvertised())
assert.Equal(t, true, routes[0].GetEnabled()) assert.True(t, routes[0].GetEnabled())
assert.Equal(t, true, routes[0].GetIsPrimary()) assert.True(t, routes[0].GetIsPrimary())
// Stop advertising route // Stop advertising route
command = []string{ command = []string{
@ -924,9 +924,9 @@ func TestEnableDisableAutoApprovedRoute(t *testing.T) {
assert.Len(t, notAdvertisedRoutes, 1) assert.Len(t, notAdvertisedRoutes, 1)
// Route is no longer advertised // Route is no longer advertised
assert.Equal(t, false, notAdvertisedRoutes[0].GetAdvertised()) assert.False(t, notAdvertisedRoutes[0].GetAdvertised())
assert.Equal(t, false, notAdvertisedRoutes[0].GetEnabled()) assert.False(t, notAdvertisedRoutes[0].GetEnabled())
assert.Equal(t, true, notAdvertisedRoutes[0].GetIsPrimary()) assert.True(t, notAdvertisedRoutes[0].GetIsPrimary())
// Advertise route again // Advertise route again
command = []string{ command = []string{
@ -955,9 +955,9 @@ func TestEnableDisableAutoApprovedRoute(t *testing.T) {
assert.Len(t, reAdvertisedRoutes, 1) assert.Len(t, reAdvertisedRoutes, 1)
// All routes should be auto approved and enabled // All routes should be auto approved and enabled
assert.Equal(t, true, reAdvertisedRoutes[0].GetAdvertised()) assert.True(t, reAdvertisedRoutes[0].GetAdvertised())
assert.Equal(t, true, reAdvertisedRoutes[0].GetEnabled()) assert.True(t, reAdvertisedRoutes[0].GetEnabled())
assert.Equal(t, true, reAdvertisedRoutes[0].GetIsPrimary()) assert.True(t, reAdvertisedRoutes[0].GetIsPrimary())
} }
func TestAutoApprovedSubRoute2068(t *testing.T) { func TestAutoApprovedSubRoute2068(t *testing.T) {
@ -1163,9 +1163,9 @@ func TestSubnetRouteACL(t *testing.T) {
assert.Len(t, routes, 1) assert.Len(t, routes, 1)
for _, route := range routes { for _, route := range routes {
assert.Equal(t, true, route.GetAdvertised()) assert.True(t, route.GetAdvertised())
assert.Equal(t, false, route.GetEnabled()) assert.False(t, route.GetEnabled())
assert.Equal(t, false, route.GetIsPrimary()) assert.False(t, route.GetIsPrimary())
} }
// Verify that no routes has been sent to the client, // Verify that no routes has been sent to the client,
@ -1212,9 +1212,9 @@ func TestSubnetRouteACL(t *testing.T) {
assert.Len(t, enablingRoutes, 1) assert.Len(t, enablingRoutes, 1)
// Node 1 has active route // Node 1 has active route
assert.Equal(t, true, enablingRoutes[0].GetAdvertised()) assert.True(t, enablingRoutes[0].GetAdvertised())
assert.Equal(t, true, enablingRoutes[0].GetEnabled()) assert.True(t, enablingRoutes[0].GetEnabled())
assert.Equal(t, true, enablingRoutes[0].GetIsPrimary()) assert.True(t, enablingRoutes[0].GetIsPrimary())
// Verify that the client has routes from the primary machine // Verify that the client has routes from the primary machine
srs1, _ := subRouter1.Status() srs1, _ := subRouter1.Status()

View file

@ -21,6 +21,7 @@ import (
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"tailscale.com/envknob" "tailscale.com/envknob"
) )
@ -205,11 +206,11 @@ func (s *Scenario) ShutdownAssertNoPanics(t *testing.T) {
if t != nil { if t != nil {
stdout, err := os.ReadFile(stdoutPath) stdout, err := os.ReadFile(stdoutPath)
assert.NoError(t, err) require.NoError(t, err)
assert.NotContains(t, string(stdout), "panic") assert.NotContains(t, string(stdout), "panic")
stderr, err := os.ReadFile(stderrPath) stderr, err := os.ReadFile(stderrPath)
assert.NoError(t, err) require.NoError(t, err)
assert.NotContains(t, string(stderr), "panic") assert.NotContains(t, string(stderr), "panic")
} }

View file

@ -1,5 +1,6 @@
---
site_name: Headscale site_name: Headscale
site_url: https://juanfont.github.io/headscale site_url: https://juanfont.github.io/headscale/
edit_uri: blob/main/docs/ # Change the master branch to main as we are using main as a main branch edit_uri: blob/main/docs/ # Change the master branch to main as we are using main as a main branch
site_author: Headscale authors site_author: Headscale authors
site_description: >- site_description: >-