mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-31 21:17:43 +09:00 
			
		
		
		
	fix webauth + autoapprove routes  (#2528)
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				Build / build-nix (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=386   GOOS=linux) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=arm   GOOS=linux GOARM=5) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=arm   GOOS=linux GOARM=6) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=arm   GOOS=linux GOARM=7) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Waiting to run
				
			
		
			
				
	
				Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Waiting to run
				
			
		
			
				
	
				Tests / test (push) Waiting to run
				
			
		
		
	
	
				
					
				
			
		
			Some checks are pending
		
		
	
	Build / build-nix (push) Waiting to run
				
			Build / build-cross (GOARCH=386   GOOS=linux) (push) Waiting to run
				
			Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Waiting to run
				
			Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Waiting to run
				
			Build / build-cross (GOARCH=arm   GOOS=linux GOARM=5) (push) Waiting to run
				
			Build / build-cross (GOARCH=arm   GOOS=linux GOARM=6) (push) Waiting to run
				
			Build / build-cross (GOARCH=arm   GOOS=linux GOARM=7) (push) Waiting to run
				
			Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Waiting to run
				
			Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Waiting to run
				
			Tests / test (push) Waiting to run
				
			* types/node: add helper funcs for node tags Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * types/node: add DebugString method for node Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * policy/v2: add String func to AutoApprover interface Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * policy/v2: simplify, use slices.Contains Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * policy/v2: debug, use nodes.DebugString Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * policy/v1: fix potential nil pointer in NodeCanApproveRoute Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * policy/v1: slices.Contains Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration/tsic: fix diff in login commands Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: fix webauth running with wrong scenario Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: move common oidc opts to func Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: require node count, more verbose Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * auth: remove uneffective route approve Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * .github/workflows: fmt Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration/tsic: add id func Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: remove call that might be nil Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: test autoapprovers against web/authkey x group/tag/user Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: unique network id per scenario Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * Revert "integration: move common oidc opts to func" This reverts commit 7e9d165d4a900c304f1083b665f1a24a26e06e55. * remove cmd Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: clean docker images between runs in ci Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration: run autoapprove test against differnt policy modes Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * integration/tsic: append, not overrwrite extra login args Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * .github/workflows: remove polv2 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
		| @@ -7,6 +7,8 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
|  | 	"slices" | ||||||
|  |  | ||||||
| 	"github.com/juanfont/headscale/hscontrol/types" | 	"github.com/juanfont/headscale/hscontrol/types" | ||||||
| 	"github.com/rs/zerolog/log" | 	"github.com/rs/zerolog/log" | ||||||
| 	"tailscale.com/tailcfg" | 	"tailscale.com/tailcfg" | ||||||
| @@ -145,13 +147,7 @@ func (pm *PolicyManager) NodeCanHaveTag(node *types.Node, tag string) bool { | |||||||
| 	tags, invalid := pm.pol.TagsOfNode(pm.users, node) | 	tags, invalid := pm.pol.TagsOfNode(pm.users, node) | ||||||
| 	log.Debug().Strs("authorised_tags", tags).Strs("unauthorised_tags", invalid).Uint64("node.id", node.ID.Uint64()).Msg("tags provided by policy") | 	log.Debug().Strs("authorised_tags", tags).Strs("unauthorised_tags", invalid).Uint64("node.id", node.ID.Uint64()).Msg("tags provided by policy") | ||||||
|  |  | ||||||
| 	for _, t := range tags { | 	return slices.Contains(tags, tag) | ||||||
| 		if t == tag { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return false |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (pm *PolicyManager) NodeCanApproveRoute(node *types.Node, route netip.Prefix) bool { | func (pm *PolicyManager) NodeCanApproveRoute(node *types.Node, route netip.Prefix) bool { | ||||||
| @@ -174,7 +170,7 @@ func (pm *PolicyManager) NodeCanApproveRoute(node *types.Node, route netip.Prefi | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// approvedIPs should contain all of node's IPs if it matches the rule, so check for first | 			// approvedIPs should contain all of node's IPs if it matches the rule, so check for first | ||||||
| 			if ips.Contains(*node.IPv4) { | 			if ips != nil && ips.Contains(*node.IPv4) { | ||||||
| 				return true | 				return true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
|  |  | ||||||
|  | 	"slices" | ||||||
|  |  | ||||||
| 	"github.com/juanfont/headscale/hscontrol/types" | 	"github.com/juanfont/headscale/hscontrol/types" | ||||||
| 	"go4.org/netipx" | 	"go4.org/netipx" | ||||||
| 	"tailscale.com/net/tsaddr" | 	"tailscale.com/net/tsaddr" | ||||||
| @@ -174,10 +176,8 @@ func (pm *PolicyManager) NodeCanHaveTag(node *types.Node, tag string) bool { | |||||||
| 	defer pm.mu.Unlock() | 	defer pm.mu.Unlock() | ||||||
|  |  | ||||||
| 	if ips, ok := pm.tagOwnerMap[Tag(tag)]; ok { | 	if ips, ok := pm.tagOwnerMap[Tag(tag)]; ok { | ||||||
| 		for _, nodeAddr := range node.IPs() { | 		if slices.ContainsFunc(node.IPs(), ips.Contains) { | ||||||
| 			if ips.Contains(nodeAddr) { | 			return true | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -196,10 +196,8 @@ func (pm *PolicyManager) NodeCanApproveRoute(node *types.Node, route netip.Prefi | |||||||
| 	// where there is an exact entry, e.g. 10.0.0.0/8, then | 	// where there is an exact entry, e.g. 10.0.0.0/8, then | ||||||
| 	// check and return quickly | 	// check and return quickly | ||||||
| 	if _, ok := pm.autoApproveMap[route]; ok { | 	if _, ok := pm.autoApproveMap[route]; ok { | ||||||
| 		for _, nodeAddr := range node.IPs() { | 		if slices.ContainsFunc(node.IPs(), pm.autoApproveMap[route].Contains) { | ||||||
| 			if pm.autoApproveMap[route].Contains(nodeAddr) { | 			return true | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -220,10 +218,8 @@ func (pm *PolicyManager) NodeCanApproveRoute(node *types.Node, route netip.Prefi | |||||||
| 		// Check if prefix is larger (so containing) and then overlaps | 		// Check if prefix is larger (so containing) and then overlaps | ||||||
| 		// the route to see if the node can approve a subset of an autoapprover | 		// the route to see if the node can approve a subset of an autoapprover | ||||||
| 		if prefix.Bits() <= route.Bits() && prefix.Overlaps(route) { | 		if prefix.Bits() <= route.Bits() && prefix.Overlaps(route) { | ||||||
| 			for _, nodeAddr := range node.IPs() { | 			if slices.ContainsFunc(node.IPs(), approveAddrs.Contains) { | ||||||
| 				if approveAddrs.Contains(nodeAddr) { | 				return true | ||||||
| 					return true |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -279,5 +275,8 @@ func (pm *PolicyManager) DebugString() string { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	sb.WriteString("\n\n") | ||||||
|  | 	sb.WriteString(pm.nodes.DebugString()) | ||||||
|  |  | ||||||
| 	return sb.String() | 	return sb.String() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -162,6 +162,10 @@ func (g Group) CanBeAutoApprover() bool { | |||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (g Group) String() string { | ||||||
|  | 	return string(g) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (g Group) Resolve(p *Policy, users types.Users, nodes types.Nodes) (*netipx.IPSet, error) { | func (g Group) Resolve(p *Policy, users types.Users, nodes types.Nodes) (*netipx.IPSet, error) { | ||||||
| 	var ips netipx.IPSetBuilder | 	var ips netipx.IPSetBuilder | ||||||
| 	var errs []error | 	var errs []error | ||||||
| @@ -235,6 +239,10 @@ func (t Tag) CanBeAutoApprover() bool { | |||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (t Tag) String() string { | ||||||
|  | 	return string(t) | ||||||
|  | } | ||||||
|  |  | ||||||
| // Host is a string that represents a hostname. | // Host is a string that represents a hostname. | ||||||
| type Host string | type Host string | ||||||
|  |  | ||||||
| @@ -590,6 +598,7 @@ func unmarshalPointer[T any]( | |||||||
| type AutoApprover interface { | type AutoApprover interface { | ||||||
| 	CanBeAutoApprover() bool | 	CanBeAutoApprover() bool | ||||||
| 	UnmarshalJSON([]byte) error | 	UnmarshalJSON([]byte) error | ||||||
|  | 	String() string | ||||||
| } | } | ||||||
|  |  | ||||||
| type AutoApprovers []AutoApprover | type AutoApprovers []AutoApprover | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
| 	"slices" | 	"slices" | ||||||
|  | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -194,19 +195,26 @@ func (node *Node) IsTagged() bool { | |||||||
| // Currently, this function only handles tags set | // Currently, this function only handles tags set | ||||||
| // via CLI ("forced tags" and preauthkeys) | // via CLI ("forced tags" and preauthkeys) | ||||||
| func (node *Node) HasTag(tag string) bool { | func (node *Node) HasTag(tag string) bool { | ||||||
| 	if slices.Contains(node.ForcedTags, tag) { | 	return slices.Contains(node.Tags(), tag) | ||||||
| 		return true | } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if node.AuthKey != nil && slices.Contains(node.AuthKey.Tags, tag) { | func (node *Node) Tags() []string { | ||||||
| 		return true | 	var tags []string | ||||||
|  |  | ||||||
|  | 	if node.AuthKey != nil { | ||||||
|  | 		tags = append(tags, node.AuthKey.Tags...) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// TODO(kradalby): Figure out how tagging should work | 	// TODO(kradalby): Figure out how tagging should work | ||||||
| 	// and hostinfo.requestedtags. | 	// and hostinfo.requestedtags. | ||||||
| 	// Do this in other work. | 	// Do this in other work. | ||||||
|  | 	// #2417 | ||||||
|  |  | ||||||
| 	return false | 	tags = append(tags, node.ForcedTags...) | ||||||
|  | 	sort.Strings(tags) | ||||||
|  | 	tags = slices.Compact(tags) | ||||||
|  |  | ||||||
|  | 	return tags | ||||||
| } | } | ||||||
|  |  | ||||||
| func (node *Node) RequestTags() []string { | func (node *Node) RequestTags() []string { | ||||||
| @@ -549,3 +557,25 @@ func (nodes Nodes) IDMap() map[NodeID]*Node { | |||||||
|  |  | ||||||
| 	return ret | 	return ret | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (nodes Nodes) DebugString() string { | ||||||
|  | 	var sb strings.Builder | ||||||
|  | 	sb.WriteString("Nodes:\n") | ||||||
|  | 	for _, node := range nodes { | ||||||
|  | 		sb.WriteString(node.DebugString()) | ||||||
|  | 		sb.WriteString("\n") | ||||||
|  | 	} | ||||||
|  | 	return sb.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (node Node) DebugString() string { | ||||||
|  | 	var sb strings.Builder | ||||||
|  | 	fmt.Fprintf(&sb, "%s(%s):\n", node.Hostname, node.ID) | ||||||
|  | 	fmt.Fprintf(&sb, "\tUser: %s (%d, %q)\n", node.User.Display(), node.User.ID, node.User.Username()) | ||||||
|  | 	fmt.Fprintf(&sb, "\tTags: %v\n", node.Tags()) | ||||||
|  | 	fmt.Fprintf(&sb, "\tIPs: %v\n", node.IPs()) | ||||||
|  | 	fmt.Fprintf(&sb, "\tApprovedRoutes: %v\n", node.ApprovedRoutes) | ||||||
|  | 	fmt.Fprintf(&sb, "\tSubnetRoutes: %v\n", node.SubnetRoutes()) | ||||||
|  | 	sb.WriteString("\n") | ||||||
|  | 	return sb.String() | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -173,3 +174,15 @@ func ParseTraceroute(output string) (Traceroute, error) { | |||||||
|  |  | ||||||
| 	return result, nil | 	return result, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func IsCI() bool { | ||||||
|  | 	if _, ok := os.LookupEnv("CI"); ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, ok := os.LookupEnv("GITHUB_RUN_ID"); ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1054,7 +1054,7 @@ func TestPolicyUpdateWhileRunningWithCLIInDatabase(t *testing.T) { | |||||||
| 	// Initially all nodes can reach each other | 	// Initially all nodes can reach each other | ||||||
| 	for _, client := range all { | 	for _, client := range all { | ||||||
| 		for _, peer := range all { | 		for _, peer := range all { | ||||||
| 			if client.ID() == peer.ID() { | 			if client.ContainerID() == peer.ContainerID() { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -442,7 +442,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { | |||||||
| 	assertNoErr(t, err) | 	assertNoErr(t, err) | ||||||
| 	assert.Len(t, listUsers, 0) | 	assert.Len(t, listUsers, 0) | ||||||
|  |  | ||||||
| 	ts, err := scenario.CreateTailscaleNode("unstable", tsic.WithNetwork(scenario.networks[TestDefaultNetwork])) | 	ts, err := scenario.CreateTailscaleNode("unstable", tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork])) | ||||||
| 	assertNoErr(t, err) | 	assertNoErr(t, err) | ||||||
|  |  | ||||||
| 	u, err := ts.LoginWithURL(headscale.GetEndpoint()) | 	u, err := ts.LoginWithURL(headscale.GetEndpoint()) | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	defer scenario.ShutdownAssertNoPanics(t) | 	defer scenario.ShutdownAssertNoPanics(t) | ||||||
|  |  | ||||||
| 	err = scenario.CreateHeadscaleEnv( | 	err = scenario.CreateHeadscaleEnvWithLoginURL( | ||||||
| 		nil, | 		nil, | ||||||
| 		hsic.WithTestName("webauthping"), | 		hsic.WithTestName("webauthping"), | ||||||
| 		hsic.WithEmbeddedDERPServerOnly(), | 		hsic.WithEmbeddedDERPServerOnly(), | ||||||
| @@ -66,7 +66,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { | |||||||
| 	assertNoErr(t, err) | 	assertNoErr(t, err) | ||||||
| 	defer scenario.ShutdownAssertNoPanics(t) | 	defer scenario.ShutdownAssertNoPanics(t) | ||||||
|  |  | ||||||
| 	err = scenario.CreateHeadscaleEnv( | 	err = scenario.CreateHeadscaleEnvWithLoginURL( | ||||||
| 		nil, | 		nil, | ||||||
| 		hsic.WithTestName("weblogout"), | 		hsic.WithTestName("weblogout"), | ||||||
| 		hsic.WithTLS(), | 		hsic.WithTLS(), | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import ( | |||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
|  |  | ||||||
|  | 	"github.com/juanfont/headscale/hscontrol/util" | ||||||
| 	"github.com/ory/dockertest/v3" | 	"github.com/ory/dockertest/v3" | ||||||
| 	"github.com/ory/dockertest/v3/docker" | 	"github.com/ory/dockertest/v3/docker" | ||||||
| ) | ) | ||||||
| @@ -105,3 +106,23 @@ func CleanUnreferencedNetworks(pool *dockertest.Pool) error { | |||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CleanImagesInCI removes images if running in CI. | ||||||
|  | func CleanImagesInCI(pool *dockertest.Pool) error { | ||||||
|  | 	if !util.IsCI() { | ||||||
|  | 		log.Println("Skipping image cleanup outside of CI") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	images, err := pool.Client.ListImages(docker.ListImagesOptions{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("getting images: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, image := range images { | ||||||
|  | 		log.Printf("removing image: %s, %v", image.ID, image.RepoTags) | ||||||
|  | 		_ = pool.Client.RemoveImage(image.ID) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -138,7 +138,7 @@ func testEphemeralWithOptions(t *testing.T, opts ...hsic.Option) { | |||||||
| 			t.Fatalf("failed to create user %s: %s", userName, err) | 			t.Fatalf("failed to create user %s: %s", userName, err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = scenario.CreateTailscaleNodesInUser(userName, "all", spec.NodesPerUser, tsic.WithNetwork(scenario.networks[TestDefaultNetwork])) | 		err = scenario.CreateTailscaleNodesInUser(userName, "all", spec.NodesPerUser, tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork])) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) | 			t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) | ||||||
| 		} | 		} | ||||||
| @@ -216,7 +216,7 @@ func TestEphemeral2006DeletedTooQuickly(t *testing.T) { | |||||||
| 			t.Fatalf("failed to create user %s: %s", userName, err) | 			t.Fatalf("failed to create user %s: %s", userName, err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = scenario.CreateTailscaleNodesInUser(userName, "all", spec.NodesPerUser, tsic.WithNetwork(scenario.networks[TestDefaultNetwork])) | 		err = scenario.CreateTailscaleNodesInUser(userName, "all", spec.NodesPerUser, tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork])) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) | 			t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -109,6 +109,9 @@ type Scenario struct { | |||||||
|  |  | ||||||
| 	spec          ScenarioSpec | 	spec          ScenarioSpec | ||||||
| 	userToNetwork map[string]*dockertest.Network | 	userToNetwork map[string]*dockertest.Network | ||||||
|  |  | ||||||
|  | 	testHashPrefix     string | ||||||
|  | 	testDefaultNetwork string | ||||||
| } | } | ||||||
|  |  | ||||||
| // ScenarioSpec describes the users, nodes, and network topology to | // ScenarioSpec describes the users, nodes, and network topology to | ||||||
| @@ -150,11 +153,8 @@ type ScenarioSpec struct { | |||||||
| 	MaxWait time.Duration | 	MaxWait time.Duration | ||||||
| } | } | ||||||
|  |  | ||||||
| var TestHashPrefix = "hs-" + util.MustGenerateRandomStringDNSSafe(scenarioHashLength) | func (s *Scenario) prefixedNetworkName(name string) string { | ||||||
| var TestDefaultNetwork = TestHashPrefix + "-default" | 	return s.testHashPrefix + "-" + name | ||||||
|  |  | ||||||
| func prefixedNetworkName(name string) string { |  | ||||||
| 	return TestHashPrefix + "-" + name |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with | // NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with | ||||||
| @@ -169,6 +169,7 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | |||||||
| 	// This might be a no op, but it is worth a try as we sometime | 	// This might be a no op, but it is worth a try as we sometime | ||||||
| 	// dont clean up nicely after ourselves. | 	// dont clean up nicely after ourselves. | ||||||
| 	dockertestutil.CleanUnreferencedNetworks(pool) | 	dockertestutil.CleanUnreferencedNetworks(pool) | ||||||
|  | 	dockertestutil.CleanImagesInCI(pool) | ||||||
|  |  | ||||||
| 	if spec.MaxWait == 0 { | 	if spec.MaxWait == 0 { | ||||||
| 		pool.MaxWait = dockertestMaxWait() | 		pool.MaxWait = dockertestMaxWait() | ||||||
| @@ -176,18 +177,22 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | |||||||
| 		pool.MaxWait = spec.MaxWait | 		pool.MaxWait = spec.MaxWait | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	testHashPrefix := "hs-" + util.MustGenerateRandomStringDNSSafe(scenarioHashLength) | ||||||
| 	s := &Scenario{ | 	s := &Scenario{ | ||||||
| 		controlServers: xsync.NewMapOf[string, ControlServer](), | 		controlServers: xsync.NewMapOf[string, ControlServer](), | ||||||
| 		users:          make(map[string]*User), | 		users:          make(map[string]*User), | ||||||
|  |  | ||||||
| 		pool: pool, | 		pool: pool, | ||||||
| 		spec: spec, | 		spec: spec, | ||||||
|  |  | ||||||
|  | 		testHashPrefix:     testHashPrefix, | ||||||
|  | 		testDefaultNetwork: testHashPrefix + "-default", | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var userToNetwork map[string]*dockertest.Network | 	var userToNetwork map[string]*dockertest.Network | ||||||
| 	if spec.Networks != nil || len(spec.Networks) != 0 { | 	if spec.Networks != nil || len(spec.Networks) != 0 { | ||||||
| 		for name, users := range s.spec.Networks { | 		for name, users := range s.spec.Networks { | ||||||
| 			networkName := TestHashPrefix + "-" + name | 			networkName := testHashPrefix + "-" + name | ||||||
| 			network, err := s.AddNetwork(networkName) | 			network, err := s.AddNetwork(networkName) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| @@ -201,7 +206,7 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		_, err := s.AddNetwork(TestDefaultNetwork) | 		_, err := s.AddNetwork(s.testDefaultNetwork) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -213,7 +218,7 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			mak.Set(&s.extraServices, prefixedNetworkName(network), append(s.extraServices[prefixedNetworkName(network)], svc)) | 			mak.Set(&s.extraServices, s.prefixedNetworkName(network), append(s.extraServices[s.prefixedNetworkName(network)], svc)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -261,7 +266,7 @@ func (s *Scenario) Networks() []*dockertest.Network { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *Scenario) Network(name string) (*dockertest.Network, error) { | func (s *Scenario) Network(name string) (*dockertest.Network, error) { | ||||||
| 	net, ok := s.networks[prefixedNetworkName(name)] | 	net, ok := s.networks[s.prefixedNetworkName(name)] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, fmt.Errorf("no network named: %s", name) | 		return nil, fmt.Errorf("no network named: %s", name) | ||||||
| 	} | 	} | ||||||
| @@ -270,7 +275,7 @@ func (s *Scenario) Network(name string) (*dockertest.Network, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *Scenario) SubnetOfNetwork(name string) (*netip.Prefix, error) { | func (s *Scenario) SubnetOfNetwork(name string) (*netip.Prefix, error) { | ||||||
| 	net, ok := s.networks[prefixedNetworkName(name)] | 	net, ok := s.networks[s.prefixedNetworkName(name)] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, fmt.Errorf("no network named: %s", name) | 		return nil, fmt.Errorf("no network named: %s", name) | ||||||
| 	} | 	} | ||||||
| @@ -288,7 +293,7 @@ func (s *Scenario) SubnetOfNetwork(name string) (*netip.Prefix, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *Scenario) Services(name string) ([]*dockertest.Resource, error) { | func (s *Scenario) Services(name string) ([]*dockertest.Resource, error) { | ||||||
| 	res, ok := s.extraServices[prefixedNetworkName(name)] | 	res, ok := s.extraServices[s.prefixedNetworkName(name)] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, fmt.Errorf("no network named: %s", name) | 		return nil, fmt.Errorf("no network named: %s", name) | ||||||
| 	} | 	} | ||||||
| @@ -298,6 +303,7 @@ func (s *Scenario) Services(name string) ([]*dockertest.Resource, error) { | |||||||
|  |  | ||||||
| func (s *Scenario) ShutdownAssertNoPanics(t *testing.T) { | func (s *Scenario) ShutdownAssertNoPanics(t *testing.T) { | ||||||
| 	defer dockertestutil.CleanUnreferencedNetworks(s.pool) | 	defer dockertestutil.CleanUnreferencedNetworks(s.pool) | ||||||
|  | 	defer dockertestutil.CleanImagesInCI(s.pool) | ||||||
|  |  | ||||||
| 	s.controlServers.Range(func(_ string, control ControlServer) bool { | 	s.controlServers.Range(func(_ string, control ControlServer) bool { | ||||||
| 		stdoutPath, stderrPath, err := control.Shutdown() | 		stdoutPath, stderrPath, err := control.Shutdown() | ||||||
| @@ -493,8 +499,7 @@ func (s *Scenario) CreateTailscaleNode( | |||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf( | 		return nil, fmt.Errorf( | ||||||
| 			"failed to create tailscale (%s) node: %w", | 			"failed to create tailscale node: %w", | ||||||
| 			tsClient.Hostname(), |  | ||||||
| 			err, | 			err, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| @@ -707,7 +712,7 @@ func (s *Scenario) createHeadscaleEnv( | |||||||
| 		if s.userToNetwork != nil { | 		if s.userToNetwork != nil { | ||||||
| 			opts = append(tsOpts, tsic.WithNetwork(s.userToNetwork[user])) | 			opts = append(tsOpts, tsic.WithNetwork(s.userToNetwork[user])) | ||||||
| 		} else { | 		} else { | ||||||
| 			opts = append(tsOpts, tsic.WithNetwork(s.networks[TestDefaultNetwork])) | 			opts = append(tsOpts, tsic.WithNetwork(s.networks[s.testDefaultNetwork])) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = s.CreateTailscaleNodesInUser(user, "all", s.spec.NodesPerUser, opts...) | 		err = s.CreateTailscaleNodesInUser(user, "all", s.spec.NodesPerUser, opts...) | ||||||
| @@ -1181,7 +1186,7 @@ func Webservice(s *Scenario, networkName string) (*dockertest.Resource, error) { | |||||||
|  |  | ||||||
| 	hostname := fmt.Sprintf("hs-webservice-%s", hash) | 	hostname := fmt.Sprintf("hs-webservice-%s", hash) | ||||||
|  |  | ||||||
| 	network, ok := s.networks[prefixedNetworkName(networkName)] | 	network, ok := s.networks[s.prefixedNetworkName(networkName)] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, fmt.Errorf("network does not exist: %s", networkName) | 		return nil, fmt.Errorf("network does not exist: %s", networkName) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -111,7 +111,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("create-tailscale", func(t *testing.T) { | 	t.Run("create-tailscale", func(t *testing.T) { | ||||||
| 		err := scenario.CreateTailscaleNodesInUser(user, "unstable", count, tsic.WithNetwork(scenario.networks[TestDefaultNetwork])) | 		err := scenario.CreateTailscaleNodesInUser(user, "unstable", count, tsic.WithNetwork(scenario.networks[scenario.testDefaultNetwork])) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatalf("failed to add tailscale nodes: %s", err) | 			t.Fatalf("failed to add tailscale nodes: %s", err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -410,7 +410,7 @@ func assertSSHHostname(t *testing.T, client TailscaleClient, peer TailscaleClien | |||||||
| 	result, _, err := doSSH(t, client, peer) | 	result, _, err := doSSH(t, client, peer) | ||||||
| 	assertNoErr(t, err) | 	assertNoErr(t, err) | ||||||
|  |  | ||||||
| 	assertContains(t, peer.ID(), strings.ReplaceAll(result, "\n", "")) | 	assertContains(t, peer.ContainerID(), strings.ReplaceAll(result, "\n", "")) | ||||||
| } | } | ||||||
|  |  | ||||||
| func assertSSHPermissionDenied(t *testing.T, client TailscaleClient, peer TailscaleClient) { | func assertSSHPermissionDenied(t *testing.T, client TailscaleClient, peer TailscaleClient) { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import ( | |||||||
| 	"net/netip" | 	"net/netip" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  |  | ||||||
|  | 	"github.com/juanfont/headscale/hscontrol/types" | ||||||
| 	"github.com/juanfont/headscale/hscontrol/util" | 	"github.com/juanfont/headscale/hscontrol/util" | ||||||
| 	"github.com/juanfont/headscale/integration/dockertestutil" | 	"github.com/juanfont/headscale/integration/dockertestutil" | ||||||
| 	"github.com/juanfont/headscale/integration/tsic" | 	"github.com/juanfont/headscale/integration/tsic" | ||||||
| @@ -43,7 +44,8 @@ type TailscaleClient interface { | |||||||
| 	Ping(hostnameOrIP string, opts ...tsic.PingOption) error | 	Ping(hostnameOrIP string, opts ...tsic.PingOption) error | ||||||
| 	Curl(url string, opts ...tsic.CurlOption) (string, error) | 	Curl(url string, opts ...tsic.CurlOption) (string, error) | ||||||
| 	Traceroute(netip.Addr) (util.Traceroute, error) | 	Traceroute(netip.Addr) (util.Traceroute, error) | ||||||
| 	ID() string | 	ContainerID() string | ||||||
|  | 	MustID() types.NodeID | ||||||
| 	ReadFile(path string) ([]byte, error) | 	ReadFile(path string) ([]byte, error) | ||||||
|  |  | ||||||
| 	// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client | 	// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/juanfont/headscale/hscontrol/types" | ||||||
| 	"github.com/juanfont/headscale/hscontrol/util" | 	"github.com/juanfont/headscale/hscontrol/util" | ||||||
| 	"github.com/juanfont/headscale/integration/dockertestutil" | 	"github.com/juanfont/headscale/integration/dockertestutil" | ||||||
| 	"github.com/juanfont/headscale/integration/integrationutil" | 	"github.com/juanfont/headscale/integration/integrationutil" | ||||||
| @@ -194,7 +195,7 @@ func WithBuildTag(tag string) Option { | |||||||
| // as part of the Login function. | // as part of the Login function. | ||||||
| func WithExtraLoginArgs(args []string) Option { | func WithExtraLoginArgs(args []string) Option { | ||||||
| 	return func(tsic *TailscaleInContainer) { | 	return func(tsic *TailscaleInContainer) { | ||||||
| 		tsic.extraLoginArgs = args | 		tsic.extraLoginArgs = append(tsic.extraLoginArgs, args...) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -383,7 +384,7 @@ func (t *TailscaleInContainer) Version() string { | |||||||
|  |  | ||||||
| // ID returns the Docker container ID of the TailscaleInContainer | // ID returns the Docker container ID of the TailscaleInContainer | ||||||
| // instance. | // instance. | ||||||
| func (t *TailscaleInContainer) ID() string { | func (t *TailscaleInContainer) ContainerID() string { | ||||||
| 	return t.container.Container.ID | 	return t.container.Container.ID | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -426,20 +427,21 @@ func (t *TailscaleInContainer) Logs(stdout, stderr io.Writer) error { | |||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Up runs the login routine on the given Tailscale instance. | func (t *TailscaleInContainer) buildLoginCommand( | ||||||
| // This login mechanism uses the authorised key for authentication. |  | ||||||
| func (t *TailscaleInContainer) Login( |  | ||||||
| 	loginServer, authKey string, | 	loginServer, authKey string, | ||||||
| ) error { | ) []string { | ||||||
| 	command := []string{ | 	command := []string{ | ||||||
| 		"tailscale", | 		"tailscale", | ||||||
| 		"up", | 		"up", | ||||||
| 		"--login-server=" + loginServer, | 		"--login-server=" + loginServer, | ||||||
| 		"--authkey=" + authKey, |  | ||||||
| 		"--hostname=" + t.hostname, | 		"--hostname=" + t.hostname, | ||||||
| 		fmt.Sprintf("--accept-routes=%t", t.withAcceptRoutes), | 		fmt.Sprintf("--accept-routes=%t", t.withAcceptRoutes), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if authKey != "" { | ||||||
|  | 		command = append(command, "--authkey="+authKey) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if t.extraLoginArgs != nil { | 	if t.extraLoginArgs != nil { | ||||||
| 		command = append(command, t.extraLoginArgs...) | 		command = append(command, t.extraLoginArgs...) | ||||||
| 	} | 	} | ||||||
| @@ -458,6 +460,16 @@ func (t *TailscaleInContainer) Login( | |||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return command | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Login runs the login routine on the given Tailscale instance. | ||||||
|  | // This login mechanism uses the authorised key for authentication. | ||||||
|  | func (t *TailscaleInContainer) Login( | ||||||
|  | 	loginServer, authKey string, | ||||||
|  | ) error { | ||||||
|  | 	command := t.buildLoginCommand(loginServer, authKey) | ||||||
|  |  | ||||||
| 	if _, _, err := t.Execute(command, dockertestutil.ExecuteCommandTimeout(dockerExecuteTimeout)); err != nil { | 	if _, _, err := t.Execute(command, dockertestutil.ExecuteCommandTimeout(dockerExecuteTimeout)); err != nil { | ||||||
| 		return fmt.Errorf( | 		return fmt.Errorf( | ||||||
| 			"%s failed to join tailscale client (%s): %w", | 			"%s failed to join tailscale client (%s): %w", | ||||||
| @@ -475,17 +487,7 @@ func (t *TailscaleInContainer) Login( | |||||||
| func (t *TailscaleInContainer) LoginWithURL( | func (t *TailscaleInContainer) LoginWithURL( | ||||||
| 	loginServer string, | 	loginServer string, | ||||||
| ) (loginURL *url.URL, err error) { | ) (loginURL *url.URL, err error) { | ||||||
| 	command := []string{ | 	command := t.buildLoginCommand(loginServer, "") | ||||||
| 		"tailscale", |  | ||||||
| 		"up", |  | ||||||
| 		"--login-server=" + loginServer, |  | ||||||
| 		"--hostname=" + t.hostname, |  | ||||||
| 		"--accept-routes=false", |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if t.extraLoginArgs != nil { |  | ||||||
| 		command = append(command, t.extraLoginArgs...) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	stdout, stderr, err := t.Execute(command) | 	stdout, stderr, err := t.Execute(command) | ||||||
| 	if errors.Is(err, errTailscaleNotLoggedIn) { | 	if errors.Is(err, errTailscaleNotLoggedIn) { | ||||||
| @@ -646,7 +648,7 @@ func (t *TailscaleInContainer) Status(save ...bool) (*ipnstate.Status, error) { | |||||||
| 	return &status, err | 	return &status, err | ||||||
| } | } | ||||||
|  |  | ||||||
| // Status returns the ipnstate.Status of the Tailscale instance. | // MustStatus returns the ipnstate.Status of the Tailscale instance. | ||||||
| func (t *TailscaleInContainer) MustStatus() *ipnstate.Status { | func (t *TailscaleInContainer) MustStatus() *ipnstate.Status { | ||||||
| 	status, err := t.Status() | 	status, err := t.Status() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -656,6 +658,21 @@ func (t *TailscaleInContainer) MustStatus() *ipnstate.Status { | |||||||
| 	return status | 	return status | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // MustID returns the ID of the Tailscale instance. | ||||||
|  | func (t *TailscaleInContainer) MustID() types.NodeID { | ||||||
|  | 	status, err := t.Status() | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	id, err := strconv.ParseUint(string(status.Self.ID), 10, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(fmt.Sprintf("failed to parse ID: %s", err)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return types.NodeID(id) | ||||||
|  | } | ||||||
|  |  | ||||||
| // Netmap returns the current Netmap (netmap.NetworkMap) of the Tailscale instance. | // Netmap returns the current Netmap (netmap.NetworkMap) of the Tailscale instance. | ||||||
| // Only works with Tailscale 1.56 and newer. | // Only works with Tailscale 1.56 and newer. | ||||||
| // Panics if version is lower then minimum. | // Panics if version is lower then minimum. | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"testing" | 	"testing" | ||||||
| @@ -344,22 +343,10 @@ func isSelfClient(client TailscaleClient, addr string) bool { | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| func isCI() bool { |  | ||||||
| 	if _, ok := os.LookupEnv("CI"); ok { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if _, ok := os.LookupEnv("GITHUB_RUN_ID"); ok { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func dockertestMaxWait() time.Duration { | func dockertestMaxWait() time.Duration { | ||||||
| 	wait := 120 * time.Second //nolint | 	wait := 120 * time.Second //nolint | ||||||
|  |  | ||||||
| 	if isCI() { | 	if util.IsCI() { | ||||||
| 		wait = 300 * time.Second //nolint | 		wait = 300 * time.Second //nolint | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user