mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	fix webauth + autoapprove routes (#2528)
* 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 7e9d165d4a.
* 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:
		
							parent
							
								
									57861507ab
								
							
						
					
					
						commit
						f1206328dc
					
				| @ -7,6 +7,8 @@ import ( | ||||
| 	"os" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"slices" | ||||
| 
 | ||||
| 	"github.com/juanfont/headscale/hscontrol/types" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"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) | ||||
| 	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 { | ||||
| 		if t == tag { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return false | ||||
| 	return slices.Contains(tags, tag) | ||||
| } | ||||
| 
 | ||||
| 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
 | ||||
| 			if ips.Contains(*node.IPv4) { | ||||
| 			if ips != nil && ips.Contains(*node.IPv4) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -7,6 +7,8 @@ import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"slices" | ||||
| 
 | ||||
| 	"github.com/juanfont/headscale/hscontrol/types" | ||||
| 	"go4.org/netipx" | ||||
| 	"tailscale.com/net/tsaddr" | ||||
| @ -174,10 +176,8 @@ func (pm *PolicyManager) NodeCanHaveTag(node *types.Node, tag string) bool { | ||||
| 	defer pm.mu.Unlock() | ||||
| 
 | ||||
| 	if ips, ok := pm.tagOwnerMap[Tag(tag)]; ok { | ||||
| 		for _, nodeAddr := range node.IPs() { | ||||
| 			if ips.Contains(nodeAddr) { | ||||
| 				return true | ||||
| 			} | ||||
| 		if slices.ContainsFunc(node.IPs(), ips.Contains) { | ||||
| 			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
 | ||||
| 	// check and return quickly
 | ||||
| 	if _, ok := pm.autoApproveMap[route]; ok { | ||||
| 		for _, nodeAddr := range node.IPs() { | ||||
| 			if pm.autoApproveMap[route].Contains(nodeAddr) { | ||||
| 				return true | ||||
| 			} | ||||
| 		if slices.ContainsFunc(node.IPs(), pm.autoApproveMap[route].Contains) { | ||||
| 			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
 | ||||
| 		// the route to see if the node can approve a subset of an autoapprover
 | ||||
| 		if prefix.Bits() <= route.Bits() && prefix.Overlaps(route) { | ||||
| 			for _, nodeAddr := range node.IPs() { | ||||
| 				if approveAddrs.Contains(nodeAddr) { | ||||
| 					return true | ||||
| 				} | ||||
| 			if slices.ContainsFunc(node.IPs(), approveAddrs.Contains) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @ -279,5 +275,8 @@ func (pm *PolicyManager) DebugString() string { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	sb.WriteString("\n\n") | ||||
| 	sb.WriteString(pm.nodes.DebugString()) | ||||
| 
 | ||||
| 	return sb.String() | ||||
| } | ||||
|  | ||||
| @ -162,6 +162,10 @@ func (g Group) CanBeAutoApprover() bool { | ||||
| 	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) { | ||||
| 	var ips netipx.IPSetBuilder | ||||
| 	var errs []error | ||||
| @ -235,6 +239,10 @@ func (t Tag) CanBeAutoApprover() bool { | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| func (t Tag) String() string { | ||||
| 	return string(t) | ||||
| } | ||||
| 
 | ||||
| // Host is a string that represents a hostname.
 | ||||
| type Host string | ||||
| 
 | ||||
| @ -590,6 +598,7 @@ func unmarshalPointer[T any]( | ||||
| type AutoApprover interface { | ||||
| 	CanBeAutoApprover() bool | ||||
| 	UnmarshalJSON([]byte) error | ||||
| 	String() string | ||||
| } | ||||
| 
 | ||||
| type AutoApprovers []AutoApprover | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"net/netip" | ||||
| 	"slices" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @ -194,19 +195,26 @@ func (node *Node) IsTagged() bool { | ||||
| // Currently, this function only handles tags set
 | ||||
| // via CLI ("forced tags" and preauthkeys)
 | ||||
| func (node *Node) HasTag(tag string) bool { | ||||
| 	if slices.Contains(node.ForcedTags, tag) { | ||||
| 		return true | ||||
| 	} | ||||
| 	return slices.Contains(node.Tags(), tag) | ||||
| } | ||||
| 
 | ||||
| 	if node.AuthKey != nil && slices.Contains(node.AuthKey.Tags, tag) { | ||||
| 		return true | ||||
| func (node *Node) Tags() []string { | ||||
| 	var tags []string | ||||
| 
 | ||||
| 	if node.AuthKey != nil { | ||||
| 		tags = append(tags, node.AuthKey.Tags...) | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(kradalby): Figure out how tagging should work
 | ||||
| 	// and hostinfo.requestedtags.
 | ||||
| 	// 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 { | ||||
| @ -549,3 +557,25 @@ func (nodes Nodes) IDMap() map[NodeID]*Node { | ||||
| 
 | ||||
| 	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" | ||||
| 	"net/netip" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @ -173,3 +174,15 @@ func ParseTraceroute(output string) (Traceroute, error) { | ||||
| 
 | ||||
| 	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
 | ||||
| 	for _, client := range all { | ||||
| 		for _, peer := range all { | ||||
| 			if client.ID() == peer.ID() { | ||||
| 			if client.ContainerID() == peer.ContainerID() { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
|  | ||||
| @ -442,7 +442,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) { | ||||
| 	assertNoErr(t, err) | ||||
| 	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) | ||||
| 
 | ||||
| 	u, err := ts.LoginWithURL(headscale.GetEndpoint()) | ||||
|  | ||||
| @ -26,7 +26,7 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { | ||||
| 	} | ||||
| 	defer scenario.ShutdownAssertNoPanics(t) | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv( | ||||
| 	err = scenario.CreateHeadscaleEnvWithLoginURL( | ||||
| 		nil, | ||||
| 		hsic.WithTestName("webauthping"), | ||||
| 		hsic.WithEmbeddedDERPServerOnly(), | ||||
| @ -66,7 +66,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { | ||||
| 	assertNoErr(t, err) | ||||
| 	defer scenario.ShutdownAssertNoPanics(t) | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv( | ||||
| 	err = scenario.CreateHeadscaleEnvWithLoginURL( | ||||
| 		nil, | ||||
| 		hsic.WithTestName("weblogout"), | ||||
| 		hsic.WithTLS(), | ||||
|  | ||||
| @ -6,6 +6,7 @@ import ( | ||||
| 	"log" | ||||
| 	"net" | ||||
| 
 | ||||
| 	"github.com/juanfont/headscale/hscontrol/util" | ||||
| 	"github.com/ory/dockertest/v3" | ||||
| 	"github.com/ory/dockertest/v3/docker" | ||||
| ) | ||||
| @ -105,3 +106,23 @@ func CleanUnreferencedNetworks(pool *dockertest.Pool) error { | ||||
| 
 | ||||
| 	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) | ||||
| 		} | ||||
| 
 | ||||
| 		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 { | ||||
| 			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) | ||||
| 		} | ||||
| 
 | ||||
| 		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 { | ||||
| 			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 | ||||
| 	userToNetwork map[string]*dockertest.Network | ||||
| 
 | ||||
| 	testHashPrefix     string | ||||
| 	testDefaultNetwork string | ||||
| } | ||||
| 
 | ||||
| // ScenarioSpec describes the users, nodes, and network topology to
 | ||||
| @ -150,11 +153,8 @@ type ScenarioSpec struct { | ||||
| 	MaxWait time.Duration | ||||
| } | ||||
| 
 | ||||
| var TestHashPrefix = "hs-" + util.MustGenerateRandomStringDNSSafe(scenarioHashLength) | ||||
| var TestDefaultNetwork = TestHashPrefix + "-default" | ||||
| 
 | ||||
| func prefixedNetworkName(name string) string { | ||||
| 	return TestHashPrefix + "-" + name | ||||
| func (s *Scenario) prefixedNetworkName(name string) string { | ||||
| 	return s.testHashPrefix + "-" + name | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
| 	// dont clean up nicely after ourselves.
 | ||||
| 	dockertestutil.CleanUnreferencedNetworks(pool) | ||||
| 	dockertestutil.CleanImagesInCI(pool) | ||||
| 
 | ||||
| 	if spec.MaxWait == 0 { | ||||
| 		pool.MaxWait = dockertestMaxWait() | ||||
| @ -176,18 +177,22 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | ||||
| 		pool.MaxWait = spec.MaxWait | ||||
| 	} | ||||
| 
 | ||||
| 	testHashPrefix := "hs-" + util.MustGenerateRandomStringDNSSafe(scenarioHashLength) | ||||
| 	s := &Scenario{ | ||||
| 		controlServers: xsync.NewMapOf[string, ControlServer](), | ||||
| 		users:          make(map[string]*User), | ||||
| 
 | ||||
| 		pool: pool, | ||||
| 		spec: spec, | ||||
| 
 | ||||
| 		testHashPrefix:     testHashPrefix, | ||||
| 		testDefaultNetwork: testHashPrefix + "-default", | ||||
| 	} | ||||
| 
 | ||||
| 	var userToNetwork map[string]*dockertest.Network | ||||
| 	if spec.Networks != nil || len(spec.Networks) != 0 { | ||||
| 		for name, users := range s.spec.Networks { | ||||
| 			networkName := TestHashPrefix + "-" + name | ||||
| 			networkName := testHashPrefix + "-" + name | ||||
| 			network, err := s.AddNetwork(networkName) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| @ -201,7 +206,7 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		_, err := s.AddNetwork(TestDefaultNetwork) | ||||
| 		_, err := s.AddNetwork(s.testDefaultNetwork) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -213,7 +218,7 @@ func NewScenario(spec ScenarioSpec) (*Scenario, error) { | ||||
| 			if err != nil { | ||||
| 				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) { | ||||
| 	net, ok := s.networks[prefixedNetworkName(name)] | ||||
| 	net, ok := s.networks[s.prefixedNetworkName(name)] | ||||
| 	if !ok { | ||||
| 		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) { | ||||
| 	net, ok := s.networks[prefixedNetworkName(name)] | ||||
| 	net, ok := s.networks[s.prefixedNetworkName(name)] | ||||
| 	if !ok { | ||||
| 		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) { | ||||
| 	res, ok := s.extraServices[prefixedNetworkName(name)] | ||||
| 	res, ok := s.extraServices[s.prefixedNetworkName(name)] | ||||
| 	if !ok { | ||||
| 		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) { | ||||
| 	defer dockertestutil.CleanUnreferencedNetworks(s.pool) | ||||
| 	defer dockertestutil.CleanImagesInCI(s.pool) | ||||
| 
 | ||||
| 	s.controlServers.Range(func(_ string, control ControlServer) bool { | ||||
| 		stdoutPath, stderrPath, err := control.Shutdown() | ||||
| @ -493,8 +499,7 @@ func (s *Scenario) CreateTailscaleNode( | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf( | ||||
| 			"failed to create tailscale (%s) node: %w", | ||||
| 			tsClient.Hostname(), | ||||
| 			"failed to create tailscale node: %w", | ||||
| 			err, | ||||
| 		) | ||||
| 	} | ||||
| @ -707,7 +712,7 @@ func (s *Scenario) createHeadscaleEnv( | ||||
| 		if s.userToNetwork != nil { | ||||
| 			opts = append(tsOpts, tsic.WithNetwork(s.userToNetwork[user])) | ||||
| 		} 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...) | ||||
| @ -1181,7 +1186,7 @@ func Webservice(s *Scenario, networkName string) (*dockertest.Resource, error) { | ||||
| 
 | ||||
| 	hostname := fmt.Sprintf("hs-webservice-%s", hash) | ||||
| 
 | ||||
| 	network, ok := s.networks[prefixedNetworkName(networkName)] | ||||
| 	network, ok := s.networks[s.prefixedNetworkName(networkName)] | ||||
| 	if !ok { | ||||
| 		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) { | ||||
| 		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 { | ||||
| 			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) | ||||
| 	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) { | ||||
|  | ||||
| @ -5,6 +5,7 @@ import ( | ||||
| 	"net/netip" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"github.com/juanfont/headscale/hscontrol/types" | ||||
| 	"github.com/juanfont/headscale/hscontrol/util" | ||||
| 	"github.com/juanfont/headscale/integration/dockertestutil" | ||||
| 	"github.com/juanfont/headscale/integration/tsic" | ||||
| @ -43,7 +44,8 @@ type TailscaleClient interface { | ||||
| 	Ping(hostnameOrIP string, opts ...tsic.PingOption) error | ||||
| 	Curl(url string, opts ...tsic.CurlOption) (string, error) | ||||
| 	Traceroute(netip.Addr) (util.Traceroute, error) | ||||
| 	ID() string | ||||
| 	ContainerID() string | ||||
| 	MustID() types.NodeID | ||||
| 	ReadFile(path string) ([]byte, error) | ||||
| 
 | ||||
| 	// FailingPeersAsString returns a formatted-ish multi-line-string of peers in the client
 | ||||
|  | ||||
| @ -18,6 +18,7 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/juanfont/headscale/hscontrol/types" | ||||
| 	"github.com/juanfont/headscale/hscontrol/util" | ||||
| 	"github.com/juanfont/headscale/integration/dockertestutil" | ||||
| 	"github.com/juanfont/headscale/integration/integrationutil" | ||||
| @ -194,7 +195,7 @@ func WithBuildTag(tag string) Option { | ||||
| // as part of the Login function.
 | ||||
| func WithExtraLoginArgs(args []string) Option { | ||||
| 	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
 | ||||
| // instance.
 | ||||
| func (t *TailscaleInContainer) ID() string { | ||||
| func (t *TailscaleInContainer) ContainerID() string { | ||||
| 	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.
 | ||||
| // This login mechanism uses the authorised key for authentication.
 | ||||
| func (t *TailscaleInContainer) Login( | ||||
| func (t *TailscaleInContainer) buildLoginCommand( | ||||
| 	loginServer, authKey string, | ||||
| ) error { | ||||
| ) []string { | ||||
| 	command := []string{ | ||||
| 		"tailscale", | ||||
| 		"up", | ||||
| 		"--login-server=" + loginServer, | ||||
| 		"--authkey=" + authKey, | ||||
| 		"--hostname=" + t.hostname, | ||||
| 		fmt.Sprintf("--accept-routes=%t", t.withAcceptRoutes), | ||||
| 	} | ||||
| 
 | ||||
| 	if authKey != "" { | ||||
| 		command = append(command, "--authkey="+authKey) | ||||
| 	} | ||||
| 
 | ||||
| 	if t.extraLoginArgs != nil { | ||||
| 		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 { | ||||
| 		return fmt.Errorf( | ||||
| 			"%s failed to join tailscale client (%s): %w", | ||||
| @ -475,17 +487,7 @@ func (t *TailscaleInContainer) Login( | ||||
| func (t *TailscaleInContainer) LoginWithURL( | ||||
| 	loginServer string, | ||||
| ) (loginURL *url.URL, err error) { | ||||
| 	command := []string{ | ||||
| 		"tailscale", | ||||
| 		"up", | ||||
| 		"--login-server=" + loginServer, | ||||
| 		"--hostname=" + t.hostname, | ||||
| 		"--accept-routes=false", | ||||
| 	} | ||||
| 
 | ||||
| 	if t.extraLoginArgs != nil { | ||||
| 		command = append(command, t.extraLoginArgs...) | ||||
| 	} | ||||
| 	command := t.buildLoginCommand(loginServer, "") | ||||
| 
 | ||||
| 	stdout, stderr, err := t.Execute(command) | ||||
| 	if errors.Is(err, errTailscaleNotLoggedIn) { | ||||
| @ -646,7 +648,7 @@ func (t *TailscaleInContainer) Status(save ...bool) (*ipnstate.Status, error) { | ||||
| 	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 { | ||||
| 	status, err := t.Status() | ||||
| 	if err != nil { | ||||
| @ -656,6 +658,21 @@ func (t *TailscaleInContainer) MustStatus() *ipnstate.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.
 | ||||
| // Only works with Tailscale 1.56 and newer.
 | ||||
| // Panics if version is lower then minimum.
 | ||||
|  | ||||
| @ -5,7 +5,6 @@ import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| @ -344,22 +343,10 @@ func isSelfClient(client TailscaleClient, addr string) bool { | ||||
| 	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 { | ||||
| 	wait := 120 * time.Second //nolint
 | ||||
| 
 | ||||
| 	if isCI() { | ||||
| 	if util.IsCI() { | ||||
| 		wait = 300 * time.Second //nolint
 | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user