mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-30 23:51:03 +01:00 
			
		
		
		
	Rename [Nn]amespace -> [Uu]ser in go code
Use gopls, ag and perl to rename all occurances of Namespace Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
		
							parent
							
								
									bafb6791d3
								
							
						
					
					
						commit
						e3a2593344
					
				
							
								
								
									
										36
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								acls.go
									
									
									
									
									
								
							| @ -424,7 +424,7 @@ func parseProtocol(protocol string) ([]int, bool, error) { | ||||
| } | ||||
| 
 | ||||
| // expandalias has an input of either | ||||
| // - a namespace | ||||
| // - a user | ||||
| // - a group | ||||
| // - a tag | ||||
| // and transform these in IPAddresses. | ||||
| @ -444,12 +444,12 @@ func expandAlias( | ||||
| 		Msg("Expanding") | ||||
| 
 | ||||
| 	if strings.HasPrefix(alias, "group:") { | ||||
| 		namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain) | ||||
| 		users, err := expandGroup(aclPolicy, alias, stripEmailDomain) | ||||
| 		if err != nil { | ||||
| 			return ips, err | ||||
| 		} | ||||
| 		for _, n := range namespaces { | ||||
| 			nodes := filterMachinesByNamespace(machines, n) | ||||
| 		for _, n := range users { | ||||
| 			nodes := filterMachinesByUser(machines, n) | ||||
| 			for _, node := range nodes { | ||||
| 				ips = append(ips, node.IPAddresses.ToStringSlice()...) | ||||
| 			} | ||||
| @ -485,8 +485,8 @@ func expandAlias( | ||||
| 		} | ||||
| 
 | ||||
| 		// filter out machines per tag owner | ||||
| 		for _, namespace := range owners { | ||||
| 			machines := filterMachinesByNamespace(machines, namespace) | ||||
| 		for _, user := range owners { | ||||
| 			machines := filterMachinesByUser(machines, user) | ||||
| 			for _, machine := range machines { | ||||
| 				hi := machine.GetHostInfo() | ||||
| 				if contains(hi.RequestTags, alias) { | ||||
| @ -498,8 +498,8 @@ func expandAlias( | ||||
| 		return ips, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// if alias is a namespace | ||||
| 	nodes := filterMachinesByNamespace(machines, alias) | ||||
| 	// if alias is a user | ||||
| 	nodes := filterMachinesByUser(machines, alias) | ||||
| 	nodes = excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias, stripEmailDomain) | ||||
| 
 | ||||
| 	for _, n := range nodes { | ||||
| @ -532,20 +532,20 @@ func expandAlias( | ||||
| } | ||||
| 
 | ||||
| // excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones | ||||
| // that are correctly tagged since they should not be listed as being in the namespace | ||||
| // we assume in this function that we only have nodes from 1 namespace. | ||||
| // that are correctly tagged since they should not be listed as being in the user | ||||
| // we assume in this function that we only have nodes from 1 user. | ||||
| func excludeCorrectlyTaggedNodes( | ||||
| 	aclPolicy ACLPolicy, | ||||
| 	nodes []Machine, | ||||
| 	namespace string, | ||||
| 	user string, | ||||
| 	stripEmailDomain bool, | ||||
| ) []Machine { | ||||
| 	out := []Machine{} | ||||
| 	tags := []string{} | ||||
| 	for tag := range aclPolicy.TagOwners { | ||||
| 		owners, _ := expandTagOwners(aclPolicy, namespace, stripEmailDomain) | ||||
| 		ns := append(owners, namespace) | ||||
| 		if contains(ns, namespace) { | ||||
| 		owners, _ := expandTagOwners(aclPolicy, user, stripEmailDomain) | ||||
| 		ns := append(owners, user) | ||||
| 		if contains(ns, user) { | ||||
| 			tags = append(tags, tag) | ||||
| 		} | ||||
| 	} | ||||
| @ -619,10 +619,10 @@ func expandPorts(portsStr string, needsWildcard bool) (*[]tailcfg.PortRange, err | ||||
| 	return &ports, nil | ||||
| } | ||||
| 
 | ||||
| func filterMachinesByNamespace(machines []Machine, namespace string) []Machine { | ||||
| func filterMachinesByUser(machines []Machine, user string) []Machine { | ||||
| 	out := []Machine{} | ||||
| 	for _, machine := range machines { | ||||
| 		if machine.Namespace.Name == namespace { | ||||
| 		if machine.User.Name == user { | ||||
| 			out = append(out, machine) | ||||
| 		} | ||||
| 	} | ||||
| @ -630,7 +630,7 @@ func filterMachinesByNamespace(machines []Machine, namespace string) []Machine { | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| // expandTagOwners will return a list of namespace. An owner can be either a namespace or a group | ||||
| // expandTagOwners will return a list of user. An owner can be either a user or a group | ||||
| // a group cannot be composed of groups. | ||||
| func expandTagOwners( | ||||
| 	aclPolicy ACLPolicy, | ||||
| @ -661,7 +661,7 @@ func expandTagOwners( | ||||
| 	return owners, nil | ||||
| } | ||||
| 
 | ||||
| // expandGroup will return the list of namespace inside the group | ||||
| // expandGroup will return the list of user inside the group | ||||
| // after some validation. | ||||
| func expandGroup( | ||||
| 	aclPolicy ACLPolicy, | ||||
|  | ||||
							
								
								
									
										212
									
								
								acls_test.go
									
									
									
									
									
								
							
							
						
						
									
										212
									
								
								acls_test.go
									
									
									
									
									
								
							| @ -77,10 +77,10 @@ func (s *Suite) TestInvalidAction(c *check.C) { | ||||
| func (s *Suite) TestSshRules(c *check.C) { | ||||
| 	envknob.Setenv("HEADSCALE_EXPERIMENTAL_FEATURE_SSH", "1") | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("user1") | ||||
| 	user, err := app.CreateUser("user1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("user1", "testmachine") | ||||
| @ -98,7 +98,7 @@ func (s *Suite) TestSshRules(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -187,10 +187,10 @@ func (s *Suite) TestInvalidTagOwners(c *check.C) { | ||||
| // match properly the IP's of the related hosts. The owner is valid and the tag is also valid. | ||||
| // the tag is matched in the Sources section. | ||||
| func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("user1") | ||||
| 	user, err := app.CreateUser("user1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("user1", "testmachine") | ||||
| @ -208,7 +208,7 @@ func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -237,10 +237,10 @@ func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) { | ||||
| // match properly the IP's of the related hosts. The owner is valid and the tag is also valid. | ||||
| // the tag is matched in the Destinations section. | ||||
| func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("user1") | ||||
| 	user, err := app.CreateUser("user1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("user1", "testmachine") | ||||
| @ -258,7 +258,7 @@ func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -284,13 +284,13 @@ func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| // need a test with: | ||||
| // tag on a host that isn't owned by a tag owners. So the namespace | ||||
| // tag on a host that isn't owned by a tag owners. So the user | ||||
| // of the host should be valid. | ||||
| func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("user1") | ||||
| func (s *Suite) TestInvalidTagValidUser(c *check.C) { | ||||
| 	user, err := app.CreateUser("user1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("user1", "testmachine") | ||||
| @ -308,7 +308,7 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -333,13 +333,13 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| // tag on a host is owned by a tag owner, the tag is valid. | ||||
| // an ACL rule is matching the tag to a namespace. It should not be valid since the | ||||
| // an ACL rule is matching the tag to a user. It should not be valid since the | ||||
| // host should be tied to the tag now. | ||||
| func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("user1") | ||||
| func (s *Suite) TestValidTagInvalidUser(c *check.C) { | ||||
| 	user, err := app.CreateUser("user1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("user1", "webserver") | ||||
| @ -357,7 +357,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "webserver", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -376,7 +376,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { | ||||
| 		DiscoKey:       "faab", | ||||
| 		Hostname:       "user", | ||||
| 		IPAddresses:    MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo2), | ||||
| @ -467,14 +467,14 @@ func (s *Suite) TestPortWildcardYAML(c *check.C) { | ||||
| 	c.Assert(rules[0].SrcIPs[0], check.Equals, "*") | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("testnamespace") | ||||
| func (s *Suite) TestPortUser(c *check.C) { | ||||
| 	user, err := app.CreateUser("testuser") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("testnamespace", "testmachine") | ||||
| 	_, err = app.GetMachine("testuser", "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 	ips, _ := app.getAvailableIPs() | ||||
| 	machine := Machine{ | ||||
| @ -483,7 +483,7 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| @ -491,7 +491,7 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 	app.db.Save(&machine) | ||||
| 
 | ||||
| 	err = app.LoadACLPolicy( | ||||
| 		"./tests/acls/acl_policy_basic_namespace_as_user.hujson", | ||||
| 		"./tests/acls/acl_policy_basic_user_as_user.hujson", | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| @ -513,13 +513,13 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("testnamespace") | ||||
| 	user, err := app.CreateUser("testuser") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("testnamespace", "testmachine") | ||||
| 	_, err = app.GetMachine("testuser", "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 	ips, _ := app.getAvailableIPs() | ||||
| 	machine := Machine{ | ||||
| @ -528,7 +528,7 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| @ -689,7 +689,7 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "expand with namespace and group", | ||||
| 			name: "expand with user and group", | ||||
| 			args: args{ | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					Groups:    Groups{"group:foo": []string{"user1", "user2"}}, | ||||
| @ -843,10 +843,10 @@ func Test_expandPorts(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Test_listMachinesInNamespace(t *testing.T) { | ||||
| func Test_listMachinesInUser(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		machines  []Machine | ||||
| 		namespace string | ||||
| 		user string | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name string | ||||
| @ -854,54 +854,54 @@ func Test_listMachinesInNamespace(t *testing.T) { | ||||
| 		want []Machine | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "1 machine in namespace", | ||||
| 			name: "1 machine in user", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ | ||||
| 					{Namespace: Namespace{Name: "joe"}}, | ||||
| 					{User: User{Name: "joe"}}, | ||||
| 				}, | ||||
| 				namespace: "joe", | ||||
| 				user: "joe", | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| 				{Namespace: Namespace{Name: "joe"}}, | ||||
| 				{User: User{Name: "joe"}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "3 machines, 2 in namespace", | ||||
| 			name: "3 machines, 2 in user", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ | ||||
| 					{ID: 1, Namespace: Namespace{Name: "joe"}}, | ||||
| 					{ID: 2, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 3, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 1, User: User{Name: "joe"}}, | ||||
| 					{ID: 2, User: User{Name: "marc"}}, | ||||
| 					{ID: 3, User: User{Name: "marc"}}, | ||||
| 				}, | ||||
| 				namespace: "marc", | ||||
| 				user: "marc", | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| 				{ID: 2, Namespace: Namespace{Name: "marc"}}, | ||||
| 				{ID: 3, Namespace: Namespace{Name: "marc"}}, | ||||
| 				{ID: 2, User: User{Name: "marc"}}, | ||||
| 				{ID: 3, User: User{Name: "marc"}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "5 machines, 0 in namespace", | ||||
| 			name: "5 machines, 0 in user", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ | ||||
| 					{ID: 1, Namespace: Namespace{Name: "joe"}}, | ||||
| 					{ID: 2, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 3, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 4, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 5, Namespace: Namespace{Name: "marc"}}, | ||||
| 					{ID: 1, User: User{Name: "joe"}}, | ||||
| 					{ID: 2, User: User{Name: "marc"}}, | ||||
| 					{ID: 3, User: User{Name: "marc"}}, | ||||
| 					{ID: 4, User: User{Name: "marc"}}, | ||||
| 					{ID: 5, User: User{Name: "marc"}}, | ||||
| 				}, | ||||
| 				namespace: "mickael", | ||||
| 				user: "mickael", | ||||
| 			}, | ||||
| 			want: []Machine{}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, test := range tests { | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			if got := filterMachinesByNamespace(test.args.machines, test.args.namespace); !reflect.DeepEqual( | ||||
| 			if got := filterMachinesByUser(test.args.machines, test.args.user); !reflect.DeepEqual( | ||||
| 				got, | ||||
| 				test.want, | ||||
| 			) { | ||||
| 				t.Errorf("listMachinesInNamespace() = %v, want %v", got, test.want) | ||||
| 				t.Errorf("listMachinesInUser() = %v, want %v", got, test.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| @ -947,25 +947,25 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -985,25 +985,25 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -1071,7 +1071,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1082,7 +1082,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1093,13 +1093,13 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -1119,25 +1119,25 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -1160,27 +1160,27 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace:  Namespace{Name: "joe"}, | ||||
| 						User:  User{Name: "joe"}, | ||||
| 						ForcedTags: []string{"tag:hr-webserver"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace:  Namespace{Name: "joe"}, | ||||
| 						User:  User{Name: "joe"}, | ||||
| 						ForcedTags: []string{"tag:hr-webserver"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy:        ACLPolicy{}, | ||||
| @ -1198,14 +1198,14 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace:  Namespace{Name: "joe"}, | ||||
| 						User:  User{Name: "joe"}, | ||||
| 						ForcedTags: []string{"tag:hr-webserver"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1216,13 +1216,13 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -1236,7 +1236,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "list host in namespace without correctly tagged servers", | ||||
| 			name: "list host in user without correctly tagged servers", | ||||
| 			args: args{ | ||||
| 				alias: "joe", | ||||
| 				machines: []Machine{ | ||||
| @ -1244,7 +1244,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1255,7 +1255,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1266,13 +1266,13 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| @ -1308,7 +1308,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		aclPolicy        ACLPolicy | ||||
| 		nodes            []Machine | ||||
| 		namespace        string | ||||
| 		user        string | ||||
| 		stripEmailDomain bool | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| @ -1328,7 +1328,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1339,7 +1339,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1350,16 +1350,16 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				namespace:        "joe", | ||||
| 				user:        "joe", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| 				{ | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")}, | ||||
| 					Namespace:   Namespace{Name: "joe"}, | ||||
| 					User:   User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -1379,7 +1379,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1390,7 +1390,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1401,16 +1401,16 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				namespace:        "joe", | ||||
| 				user:        "joe", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| 				{ | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")}, | ||||
| 					Namespace:   Namespace{Name: "joe"}, | ||||
| 					User:   User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -1425,7 +1425,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "foo", | ||||
| @ -1436,23 +1436,23 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace:  Namespace{Name: "joe"}, | ||||
| 						User:  User{Name: "joe"}, | ||||
| 						ForcedTags: []string{"tag:accountant-webserver"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				namespace:        "joe", | ||||
| 				user:        "joe", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| 				{ | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")}, | ||||
| 					Namespace:   Namespace{Name: "joe"}, | ||||
| 					User:   User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -1467,7 +1467,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "hr-web1", | ||||
| @ -1478,7 +1478,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 						HostInfo: HostInfo{ | ||||
| 							OS:          "centos", | ||||
| 							Hostname:    "hr-web2", | ||||
| @ -1489,10 +1489,10 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.4"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				namespace:        "joe", | ||||
| 				user:        "joe", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want: []Machine{ | ||||
| @ -1500,7 +1500,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 					HostInfo: HostInfo{ | ||||
| 						OS:          "centos", | ||||
| 						Hostname:    "hr-web1", | ||||
| @ -1511,7 +1511,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.2"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 					HostInfo: HostInfo{ | ||||
| 						OS:          "centos", | ||||
| 						Hostname:    "hr-web2", | ||||
| @ -1522,7 +1522,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.4"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -1532,7 +1532,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) { | ||||
| 			got := excludeCorrectlyTaggedNodes( | ||||
| 				test.args.aclPolicy, | ||||
| 				test.args.nodes, | ||||
| 				test.args.namespace, | ||||
| 				test.args.user, | ||||
| 				test.args.stripEmailDomain, | ||||
| 			) | ||||
| 			if !reflect.DeepEqual(got, test.want) { | ||||
|  | ||||
| @ -34,7 +34,7 @@ type Groups map[string][]string | ||||
| // Hosts are alias for IP addresses or subnets. | ||||
| type Hosts map[string]netip.Prefix | ||||
| 
 | ||||
| // TagOwners specify what users (namespaces?) are allow to use certain tags. | ||||
| // TagOwners specify what users (users?) are allow to use certain tags. | ||||
| type TagOwners map[string][]string | ||||
| 
 | ||||
| // ACLTest is not implemented, but should be use to check if a certain rule is allowed. | ||||
| @ -44,7 +44,7 @@ type ACLTest struct { | ||||
| 	Deny   []string `json:"deny,omitempty" yaml:"deny,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // AutoApprovers specify which users (namespaces?), groups or tags have their advertised routes | ||||
| // AutoApprovers specify which users (users?), groups or tags have their advertised routes | ||||
| // or exit node status automatically enabled. | ||||
| type AutoApprovers struct { | ||||
| 	Routes   map[string][]string `json:"routes"   yaml:"routes"` | ||||
| @ -119,7 +119,7 @@ func (policy ACLPolicy) IsZero() bool { | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // Returns the list of autoApproving namespaces, groups or tags for a given IPPrefix. | ||||
| // Returns the list of autoApproving users, groups or tags for a given IPPrefix. | ||||
| func (autoApprovers *AutoApprovers) GetRouteApprovers( | ||||
| 	prefix netip.Prefix, | ||||
| ) ([]string, error) { | ||||
|  | ||||
| @ -29,7 +29,7 @@ type APIKey struct { | ||||
| 	LastSeen   *time.Time | ||||
| } | ||||
| 
 | ||||
| // CreateAPIKey creates a new ApiKey in a namespace, and returns it. | ||||
| // CreateAPIKey creates a new ApiKey in a user, and returns it. | ||||
| func (h *Headscale) CreateAPIKey( | ||||
| 	expiration *time.Time, | ||||
| ) (string, *APIKey, error) { | ||||
| @ -64,7 +64,7 @@ func (h *Headscale) CreateAPIKey( | ||||
| 	return keyStr, &key, nil | ||||
| } | ||||
| 
 | ||||
| // ListAPIKeys returns the list of ApiKeys for a namespace. | ||||
| // ListAPIKeys returns the list of ApiKeys for a user. | ||||
| func (h *Headscale) ListAPIKeys() ([]APIKey, error) { | ||||
| 	keys := []APIKey{} | ||||
| 	if err := h.db.Find(&keys).Error; err != nil { | ||||
|  | ||||
							
								
								
									
										48
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								app.go
									
									
									
									
									
								
							| @ -238,20 +238,20 @@ func (h *Headscale) failoverSubnetRoutes(milliSeconds int64) { | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) expireEphemeralNodesWorker() { | ||||
| 	namespaces, err := h.ListNamespaces() | ||||
| 	users, err := h.ListUsers() | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msg("Error listing namespaces") | ||||
| 		log.Error().Err(err).Msg("Error listing users") | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	for _, namespace := range namespaces { | ||||
| 		machines, err := h.ListMachinesInNamespace(namespace.Name) | ||||
| 	for _, user := range users { | ||||
| 		machines, err := h.ListMachinesByUser(user.Name) | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Err(err). | ||||
| 				Str("namespace", namespace.Name). | ||||
| 				Msg("Error listing machines in namespace") | ||||
| 				Str("user", user.Name). | ||||
| 				Msg("Error listing machines in user") | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -283,20 +283,20 @@ func (h *Headscale) expireEphemeralNodesWorker() { | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) expireExpiredMachinesWorker() { | ||||
| 	namespaces, err := h.ListNamespaces() | ||||
| 	users, err := h.ListUsers() | ||||
| 	if err != nil { | ||||
| 		log.Error().Err(err).Msg("Error listing namespaces") | ||||
| 		log.Error().Err(err).Msg("Error listing users") | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	for _, namespace := range namespaces { | ||||
| 		machines, err := h.ListMachinesInNamespace(namespace.Name) | ||||
| 	for _, user := range users { | ||||
| 		machines, err := h.ListMachinesByUser(user.Name) | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Err(err). | ||||
| 				Str("namespace", namespace.Name). | ||||
| 				Msg("Error listing machines in namespace") | ||||
| 				Str("user", user.Name). | ||||
| 				Msg("Error listing machines in user") | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -304,7 +304,7 @@ func (h *Headscale) expireExpiredMachinesWorker() { | ||||
| 		expiredFound := false | ||||
| 		for index, machine := range machines { | ||||
| 			if machine.isExpired() && | ||||
| 				machine.Expiry.After(h.getLastStateChange(namespace)) { | ||||
| 				machine.Expiry.After(h.getLastStateChange(user)) { | ||||
| 				expiredFound = true | ||||
| 
 | ||||
| 				err := h.ExpireMachine(&machines[index]) | ||||
| @ -908,31 +908,31 @@ func (h *Headscale) setLastStateChangeToNow() { | ||||
| 
 | ||||
| 	now := time.Now().UTC() | ||||
| 
 | ||||
| 	namespaces, err := h.ListNamespaces() | ||||
| 	users, err := h.ListUsers() | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Msg("failed to fetch all namespaces, failing to update last changed state.") | ||||
| 			Msg("failed to fetch all users, failing to update last changed state.") | ||||
| 	} | ||||
| 
 | ||||
| 	for _, namespace := range namespaces { | ||||
| 		lastStateUpdate.WithLabelValues(namespace.Name, "headscale").Set(float64(now.Unix())) | ||||
| 	for _, user := range users { | ||||
| 		lastStateUpdate.WithLabelValues(user.Name, "headscale").Set(float64(now.Unix())) | ||||
| 		if h.lastStateChange == nil { | ||||
| 			h.lastStateChange = xsync.NewMapOf[time.Time]() | ||||
| 		} | ||||
| 		h.lastStateChange.Store(namespace.Name, now) | ||||
| 		h.lastStateChange.Store(user.Name, now) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) getLastStateChange(namespaces ...Namespace) time.Time { | ||||
| func (h *Headscale) getLastStateChange(users ...User) time.Time { | ||||
| 	times := []time.Time{} | ||||
| 
 | ||||
| 	// getLastStateChange takes a list of namespaces as a "filter", if no namespaces | ||||
| 	// are past, then use the entier list of namespaces and look for the last update | ||||
| 	if len(namespaces) > 0 { | ||||
| 		for _, namespace := range namespaces { | ||||
| 			if lastChange, ok := h.lastStateChange.Load(namespace.Name); ok { | ||||
| 	// getLastStateChange takes a list of users as a "filter", if no users | ||||
| 	// are past, then use the entier list of users and look for the last update | ||||
| 	if len(users) > 0 { | ||||
| 		for _, user := range users { | ||||
| 			if lastChange, ok := h.lastStateChange.Load(user.Name); ok { | ||||
| 				times = append(times, lastChange) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -77,7 +77,7 @@ func main() { | ||||
| 		"TestCreateTailscale", | ||||
| 		"TestEnablingRoutes", | ||||
| 		"TestHeadscale", | ||||
| 		"TestNamespaceCommand", | ||||
| 		"TestUserCommand", | ||||
| 		"TestOIDCAuthenticationPingAll", | ||||
| 		"TestOIDCExpireNodes", | ||||
| 		"TestPingAllByHostname", | ||||
| @ -87,10 +87,10 @@ func main() { | ||||
| 		"TestPreAuthKeyCommandWithoutExpiry", | ||||
| 		"TestResolveMagicDNS", | ||||
| 		"TestSSHIsBlockedInACL", | ||||
| 		"TestSSHMultipleNamespacesAllToAll", | ||||
| 		"TestSSHMultipleUsersAllToAll", | ||||
| 		"TestSSHNoSSHConfigured", | ||||
| 		"TestSSHOneNamespaceAllToAll", | ||||
| 		"TestSSNamespaceOnlyIsolation", | ||||
| 		"TestSSHOneUserAllToAll", | ||||
| 		"TestSSUserOnlyIsolation", | ||||
| 		"TestTaildrop", | ||||
| 		"TestTailscaleNodesJoiningHeadcale", | ||||
| 	} | ||||
|  | ||||
| @ -27,8 +27,8 @@ func init() { | ||||
| 	if err != nil { | ||||
| 		log.Fatal().Err(err).Msg("") | ||||
| 	} | ||||
| 	createNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") | ||||
| 	err = createNodeCmd.MarkFlagRequired("namespace") | ||||
| 	createNodeCmd.Flags().StringP("user", "n", "", "User") | ||||
| 	err = createNodeCmd.MarkFlagRequired("user") | ||||
| 	if err != nil { | ||||
| 		log.Fatal().Err(err).Msg("") | ||||
| 	} | ||||
| @ -55,9 +55,9 @@ var createNodeCmd = &cobra.Command{ | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 
 | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -112,7 +112,7 @@ var createNodeCmd = &cobra.Command{ | ||||
| 		request := &v1.DebugCreateMachineRequest{ | ||||
| 			Key:       machineKey, | ||||
| 			Name:      name, | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 			Routes:    routes, | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
| @ -13,26 +13,26 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(namespaceCmd) | ||||
| 	namespaceCmd.AddCommand(createNamespaceCmd) | ||||
| 	namespaceCmd.AddCommand(listNamespacesCmd) | ||||
| 	namespaceCmd.AddCommand(destroyNamespaceCmd) | ||||
| 	namespaceCmd.AddCommand(renameNamespaceCmd) | ||||
| 	rootCmd.AddCommand(userCmd) | ||||
| 	userCmd.AddCommand(createUserCmd) | ||||
| 	userCmd.AddCommand(listUsersCmd) | ||||
| 	userCmd.AddCommand(destroyUserCmd) | ||||
| 	userCmd.AddCommand(renameUserCmd) | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	errMissingParameter = headscale.Error("missing parameters") | ||||
| ) | ||||
| 
 | ||||
| var namespaceCmd = &cobra.Command{ | ||||
| 	Use:     "namespaces", | ||||
| 	Short:   "Manage the namespaces of Headscale", | ||||
| 	Aliases: []string{"namespace", "ns", "user", "users"}, | ||||
| var userCmd = &cobra.Command{ | ||||
| 	Use:     "users", | ||||
| 	Short:   "Manage the users of Headscale", | ||||
| 	Aliases: []string{"user", "ns", "user", "users"}, | ||||
| } | ||||
| 
 | ||||
| var createNamespaceCmd = &cobra.Command{ | ||||
| var createUserCmd = &cobra.Command{ | ||||
| 	Use:     "create NAME", | ||||
| 	Short:   "Creates a new namespace", | ||||
| 	Short:   "Creates a new user", | ||||
| 	Aliases: []string{"c", "new"}, | ||||
| 	Args: func(cmd *cobra.Command, args []string) error { | ||||
| 		if len(args) < 1 { | ||||
| @ -44,7 +44,7 @@ var createNamespaceCmd = &cobra.Command{ | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 
 | ||||
| 		namespaceName := args[0] | ||||
| 		userName := args[0] | ||||
| 
 | ||||
| 		ctx, client, conn, cancel := getHeadscaleCLIClient() | ||||
| 		defer cancel() | ||||
| @ -52,15 +52,15 @@ var createNamespaceCmd = &cobra.Command{ | ||||
| 
 | ||||
| 		log.Trace().Interface("client", client).Msg("Obtained gRPC client") | ||||
| 
 | ||||
| 		request := &v1.CreateNamespaceRequest{Name: namespaceName} | ||||
| 		request := &v1.CreateUserRequest{Name: userName} | ||||
| 
 | ||||
| 		log.Trace().Interface("request", request).Msg("Sending CreateNamespace request") | ||||
| 		response, err := client.CreateNamespace(ctx, request) | ||||
| 		log.Trace().Interface("request", request).Msg("Sending CreateUser request") | ||||
| 		response, err := client.CreateUser(ctx, request) | ||||
| 		if err != nil { | ||||
| 			ErrorOutput( | ||||
| 				err, | ||||
| 				fmt.Sprintf( | ||||
| 					"Cannot create namespace: %s", | ||||
| 					"Cannot create user: %s", | ||||
| 					status.Convert(err).Message(), | ||||
| 				), | ||||
| 				output, | ||||
| @ -69,13 +69,13 @@ var createNamespaceCmd = &cobra.Command{ | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		SuccessOutput(response.Namespace, "Namespace created", output) | ||||
| 		SuccessOutput(response.User, "User created", output) | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var destroyNamespaceCmd = &cobra.Command{ | ||||
| var destroyUserCmd = &cobra.Command{ | ||||
| 	Use:     "destroy NAME", | ||||
| 	Short:   "Destroys a namespace", | ||||
| 	Short:   "Destroys a user", | ||||
| 	Aliases: []string{"delete"}, | ||||
| 	Args: func(cmd *cobra.Command, args []string) error { | ||||
| 		if len(args) < 1 { | ||||
| @ -87,17 +87,17 @@ var destroyNamespaceCmd = &cobra.Command{ | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 
 | ||||
| 		namespaceName := args[0] | ||||
| 		userName := args[0] | ||||
| 
 | ||||
| 		request := &v1.GetNamespaceRequest{ | ||||
| 			Name: namespaceName, | ||||
| 		request := &v1.GetUserRequest{ | ||||
| 			Name: userName, | ||||
| 		} | ||||
| 
 | ||||
| 		ctx, client, conn, cancel := getHeadscaleCLIClient() | ||||
| 		defer cancel() | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		_, err := client.GetNamespace(ctx, request) | ||||
| 		_, err := client.GetUser(ctx, request) | ||||
| 		if err != nil { | ||||
| 			ErrorOutput( | ||||
| 				err, | ||||
| @ -113,8 +113,8 @@ var destroyNamespaceCmd = &cobra.Command{ | ||||
| 		if !force { | ||||
| 			prompt := &survey.Confirm{ | ||||
| 				Message: fmt.Sprintf( | ||||
| 					"Do you want to remove the namespace '%s' and any associated preauthkeys?", | ||||
| 					namespaceName, | ||||
| 					"Do you want to remove the user '%s' and any associated preauthkeys?", | ||||
| 					userName, | ||||
| 				), | ||||
| 			} | ||||
| 			err := survey.AskOne(prompt, &confirm) | ||||
| @ -124,14 +124,14 @@ var destroyNamespaceCmd = &cobra.Command{ | ||||
| 		} | ||||
| 
 | ||||
| 		if confirm || force { | ||||
| 			request := &v1.DeleteNamespaceRequest{Name: namespaceName} | ||||
| 			request := &v1.DeleteUserRequest{Name: userName} | ||||
| 
 | ||||
| 			response, err := client.DeleteNamespace(ctx, request) | ||||
| 			response, err := client.DeleteUser(ctx, request) | ||||
| 			if err != nil { | ||||
| 				ErrorOutput( | ||||
| 					err, | ||||
| 					fmt.Sprintf( | ||||
| 						"Cannot destroy namespace: %s", | ||||
| 						"Cannot destroy user: %s", | ||||
| 						status.Convert(err).Message(), | ||||
| 					), | ||||
| 					output, | ||||
| @ -139,16 +139,16 @@ var destroyNamespaceCmd = &cobra.Command{ | ||||
| 
 | ||||
| 				return | ||||
| 			} | ||||
| 			SuccessOutput(response, "Namespace destroyed", output) | ||||
| 			SuccessOutput(response, "User destroyed", output) | ||||
| 		} else { | ||||
| 			SuccessOutput(map[string]string{"Result": "Namespace not destroyed"}, "Namespace not destroyed", output) | ||||
| 			SuccessOutput(map[string]string{"Result": "User not destroyed"}, "User not destroyed", output) | ||||
| 		} | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var listNamespacesCmd = &cobra.Command{ | ||||
| var listUsersCmd = &cobra.Command{ | ||||
| 	Use:     "list", | ||||
| 	Short:   "List all the namespaces", | ||||
| 	Short:   "List all the users", | ||||
| 	Aliases: []string{"ls", "show"}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| @ -157,13 +157,13 @@ var listNamespacesCmd = &cobra.Command{ | ||||
| 		defer cancel() | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		request := &v1.ListNamespacesRequest{} | ||||
| 		request := &v1.ListUsersRequest{} | ||||
| 
 | ||||
| 		response, err := client.ListNamespaces(ctx, request) | ||||
| 		response, err := client.ListUsers(ctx, request) | ||||
| 		if err != nil { | ||||
| 			ErrorOutput( | ||||
| 				err, | ||||
| 				fmt.Sprintf("Cannot get namespaces: %s", status.Convert(err).Message()), | ||||
| 				fmt.Sprintf("Cannot get users: %s", status.Convert(err).Message()), | ||||
| 				output, | ||||
| 			) | ||||
| 
 | ||||
| @ -171,19 +171,19 @@ var listNamespacesCmd = &cobra.Command{ | ||||
| 		} | ||||
| 
 | ||||
| 		if output != "" { | ||||
| 			SuccessOutput(response.Namespaces, "", output) | ||||
| 			SuccessOutput(response.Users, "", output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		tableData := pterm.TableData{{"ID", "Name", "Created"}} | ||||
| 		for _, namespace := range response.GetNamespaces() { | ||||
| 		for _, user := range response.GetUsers() { | ||||
| 			tableData = append( | ||||
| 				tableData, | ||||
| 				[]string{ | ||||
| 					namespace.GetId(), | ||||
| 					namespace.GetName(), | ||||
| 					namespace.GetCreatedAt().AsTime().Format("2006-01-02 15:04:05"), | ||||
| 					user.GetId(), | ||||
| 					user.GetName(), | ||||
| 					user.GetCreatedAt().AsTime().Format("2006-01-02 15:04:05"), | ||||
| 				}, | ||||
| 			) | ||||
| 		} | ||||
| @ -200,9 +200,9 @@ var listNamespacesCmd = &cobra.Command{ | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var renameNamespaceCmd = &cobra.Command{ | ||||
| var renameUserCmd = &cobra.Command{ | ||||
| 	Use:     "rename OLD_NAME NEW_NAME", | ||||
| 	Short:   "Renames a namespace", | ||||
| 	Short:   "Renames a user", | ||||
| 	Aliases: []string{"mv"}, | ||||
| 	Args: func(cmd *cobra.Command, args []string) error { | ||||
| 		expectedArguments := 2 | ||||
| @ -219,17 +219,17 @@ var renameNamespaceCmd = &cobra.Command{ | ||||
| 		defer cancel() | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		request := &v1.RenameNamespaceRequest{ | ||||
| 		request := &v1.RenameUserRequest{ | ||||
| 			OldName: args[0], | ||||
| 			NewName: args[1], | ||||
| 		} | ||||
| 
 | ||||
| 		response, err := client.RenameNamespace(ctx, request) | ||||
| 		response, err := client.RenameUser(ctx, request) | ||||
| 		if err != nil { | ||||
| 			ErrorOutput( | ||||
| 				err, | ||||
| 				fmt.Sprintf( | ||||
| 					"Cannot rename namespace: %s", | ||||
| 					"Cannot rename user: %s", | ||||
| 					status.Convert(err).Message(), | ||||
| 				), | ||||
| 				output, | ||||
| @ -238,6 +238,6 @@ var renameNamespaceCmd = &cobra.Command{ | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		SuccessOutput(response.Namespace, "Namespace renamed", output) | ||||
| 		SuccessOutput(response.User, "User renamed", output) | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| @ -19,12 +19,12 @@ import ( | ||||
| 
 | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(nodeCmd) | ||||
| 	listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") | ||||
| 	listNodesCmd.Flags().StringP("user", "n", "", "Filter by user") | ||||
| 	listNodesCmd.Flags().BoolP("tags", "t", false, "Show tags") | ||||
| 	nodeCmd.AddCommand(listNodesCmd) | ||||
| 
 | ||||
| 	registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") | ||||
| 	err := registerNodeCmd.MarkFlagRequired("namespace") | ||||
| 	registerNodeCmd.Flags().StringP("user", "n", "", "User") | ||||
| 	err := registerNodeCmd.MarkFlagRequired("user") | ||||
| 	if err != nil { | ||||
| 		log.Fatalf(err.Error()) | ||||
| 	} | ||||
| @ -63,9 +63,9 @@ func init() { | ||||
| 		log.Fatalf(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	moveNodeCmd.Flags().StringP("namespace", "n", "", "New namespace") | ||||
| 	moveNodeCmd.Flags().StringP("user", "n", "", "New user") | ||||
| 
 | ||||
| 	err = moveNodeCmd.MarkFlagRequired("namespace") | ||||
| 	err = moveNodeCmd.MarkFlagRequired("user") | ||||
| 	if err != nil { | ||||
| 		log.Fatalf(err.Error()) | ||||
| 	} | ||||
| @ -93,9 +93,9 @@ var registerNodeCmd = &cobra.Command{ | ||||
| 	Short: "Registers a machine to your network", | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -117,7 +117,7 @@ var registerNodeCmd = &cobra.Command{ | ||||
| 
 | ||||
| 		request := &v1.RegisterMachineRequest{ | ||||
| 			Key:       machineKey, | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 		} | ||||
| 
 | ||||
| 		response, err := client.RegisterMachine(ctx, request) | ||||
| @ -146,9 +146,9 @@ var listNodesCmd = &cobra.Command{ | ||||
| 	Aliases: []string{"ls", "show"}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -164,7 +164,7 @@ var listNodesCmd = &cobra.Command{ | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		request := &v1.ListMachinesRequest{ | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 		} | ||||
| 
 | ||||
| 		response, err := client.ListMachines(ctx, request) | ||||
| @ -184,7 +184,7 @@ var listNodesCmd = &cobra.Command{ | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		tableData, err := nodesToPtables(namespace, showTags, response.Machines) | ||||
| 		tableData, err := nodesToPtables(user, showTags, response.Machines) | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output) | ||||
| 
 | ||||
| @ -388,7 +388,7 @@ var deleteNodeCmd = &cobra.Command{ | ||||
| 
 | ||||
| var moveNodeCmd = &cobra.Command{ | ||||
| 	Use:     "move", | ||||
| 	Short:   "Move node to another namespace", | ||||
| 	Short:   "Move node to another user", | ||||
| 	Aliases: []string{"mv"}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| @ -404,11 +404,11 @@ var moveNodeCmd = &cobra.Command{ | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput( | ||||
| 				err, | ||||
| 				fmt.Sprintf("Error getting namespace: %s", err), | ||||
| 				fmt.Sprintf("Error getting user: %s", err), | ||||
| 				output, | ||||
| 			) | ||||
| 
 | ||||
| @ -439,7 +439,7 @@ var moveNodeCmd = &cobra.Command{ | ||||
| 
 | ||||
| 		moveRequest := &v1.MoveMachineRequest{ | ||||
| 			MachineId: identifier, | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 		} | ||||
| 
 | ||||
| 		moveResponse, err := client.MoveMachine(ctx, moveRequest) | ||||
| @ -456,12 +456,12 @@ var moveNodeCmd = &cobra.Command{ | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		SuccessOutput(moveResponse.Machine, "Node moved to another namespace", output) | ||||
| 		SuccessOutput(moveResponse.Machine, "Node moved to another user", output) | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| func nodesToPtables( | ||||
| 	currentNamespace string, | ||||
| 	currentUser string, | ||||
| 	showTags bool, | ||||
| 	machines []*v1.Machine, | ||||
| ) (pterm.TableData, error) { | ||||
| @ -471,7 +471,7 @@ func nodesToPtables( | ||||
| 		"Name", | ||||
| 		"MachineKey", | ||||
| 		"NodeKey", | ||||
| 		"Namespace", | ||||
| 		"User", | ||||
| 		"IP addresses", | ||||
| 		"Ephemeral", | ||||
| 		"Last seen", | ||||
| @ -560,12 +560,12 @@ func nodesToPtables( | ||||
| 		} | ||||
| 		validTags = strings.TrimLeft(validTags, ",") | ||||
| 
 | ||||
| 		var namespace string | ||||
| 		if currentNamespace == "" || (currentNamespace == machine.Namespace.Name) { | ||||
| 			namespace = pterm.LightMagenta(machine.Namespace.Name) | ||||
| 		var user string | ||||
| 		if currentUser == "" || (currentUser == machine.User.Name) { | ||||
| 			user = pterm.LightMagenta(machine.User.Name) | ||||
| 		} else { | ||||
| 			// Shared into this namespace | ||||
| 			namespace = pterm.LightYellow(machine.Namespace.Name) | ||||
| 			// Shared into this user | ||||
| 			user = pterm.LightYellow(machine.User.Name) | ||||
| 		} | ||||
| 
 | ||||
| 		var IPV4Address string | ||||
| @ -584,7 +584,7 @@ func nodesToPtables( | ||||
| 			machine.GetGivenName(), | ||||
| 			machineKey.ShortString(), | ||||
| 			nodeKey.ShortString(), | ||||
| 			namespace, | ||||
| 			user, | ||||
| 			strings.Join([]string{IPV4Address, IPV6Address}, ", "), | ||||
| 			strconv.FormatBool(ephemeral), | ||||
| 			lastSeenTime, | ||||
|  | ||||
| @ -20,8 +20,8 @@ const ( | ||||
| 
 | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(preauthkeysCmd) | ||||
| 	preauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace") | ||||
| 	err := preauthkeysCmd.MarkPersistentFlagRequired("namespace") | ||||
| 	preauthkeysCmd.PersistentFlags().StringP("user", "n", "", "User") | ||||
| 	err := preauthkeysCmd.MarkPersistentFlagRequired("user") | ||||
| 	if err != nil { | ||||
| 		log.Fatal().Err(err).Msg("") | ||||
| 	} | ||||
| @ -46,14 +46,14 @@ var preauthkeysCmd = &cobra.Command{ | ||||
| 
 | ||||
| var listPreAuthKeys = &cobra.Command{ | ||||
| 	Use:     "list", | ||||
| 	Short:   "List the preauthkeys for this namespace", | ||||
| 	Short:   "List the preauthkeys for this user", | ||||
| 	Aliases: []string{"ls", "show"}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 
 | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -63,7 +63,7 @@ var listPreAuthKeys = &cobra.Command{ | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		request := &v1.ListPreAuthKeysRequest{ | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 		} | ||||
| 
 | ||||
| 		response, err := client.ListPreAuthKeys(ctx, request) | ||||
| @ -143,14 +143,14 @@ var listPreAuthKeys = &cobra.Command{ | ||||
| 
 | ||||
| var createPreAuthKeyCmd = &cobra.Command{ | ||||
| 	Use:     "create", | ||||
| 	Short:   "Creates a new preauthkey in the specified namespace", | ||||
| 	Short:   "Creates a new preauthkey in the specified user", | ||||
| 	Aliases: []string{"c", "new"}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 
 | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -162,11 +162,11 @@ var createPreAuthKeyCmd = &cobra.Command{ | ||||
| 		log.Trace(). | ||||
| 			Bool("reusable", reusable). | ||||
| 			Bool("ephemeral", ephemeral). | ||||
| 			Str("namespace", namespace). | ||||
| 			Str("user", user). | ||||
| 			Msg("Preparing to create preauthkey") | ||||
| 
 | ||||
| 		request := &v1.CreatePreAuthKeyRequest{ | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 			Reusable:  reusable, | ||||
| 			Ephemeral: ephemeral, | ||||
| 			AclTags:   tags, | ||||
| @ -225,9 +225,9 @@ var expirePreAuthKeyCmd = &cobra.Command{ | ||||
| 	}, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		output, _ := cmd.Flags().GetString("output") | ||||
| 		namespace, err := cmd.Flags().GetString("namespace") | ||||
| 		user, err := cmd.Flags().GetString("user") | ||||
| 		if err != nil { | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output) | ||||
| 			ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| @ -237,7 +237,7 @@ var expirePreAuthKeyCmd = &cobra.Command{ | ||||
| 		defer conn.Close() | ||||
| 
 | ||||
| 		request := &v1.ExpirePreAuthKeyRequest{ | ||||
| 			Namespace: namespace, | ||||
| 			User: user, | ||||
| 			Key:       args[0], | ||||
| 		} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										14
									
								
								dns.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								dns.go
									
									
									
									
									
								
							| @ -190,23 +190,23 @@ func getMapResponseDNSConfig( | ||||
| ) *tailcfg.DNSConfig { | ||||
| 	var dnsConfig *tailcfg.DNSConfig = dnsConfigOrig.Clone() | ||||
| 	if dnsConfigOrig != nil && dnsConfigOrig.Proxied { // if MagicDNS is enabled | ||||
| 		// Only inject the Search Domain of the current namespace - shared nodes should use their full FQDN | ||||
| 		// Only inject the Search Domain of the current user - shared nodes should use their full FQDN | ||||
| 		dnsConfig.Domains = append( | ||||
| 			dnsConfig.Domains, | ||||
| 			fmt.Sprintf( | ||||
| 				"%s.%s", | ||||
| 				machine.Namespace.Name, | ||||
| 				machine.User.Name, | ||||
| 				baseDomain, | ||||
| 			), | ||||
| 		) | ||||
| 
 | ||||
| 		namespaceSet := mapset.NewSet[Namespace]() | ||||
| 		namespaceSet.Add(machine.Namespace) | ||||
| 		userSet := mapset.NewSet[User]() | ||||
| 		userSet.Add(machine.User) | ||||
| 		for _, p := range peers { | ||||
| 			namespaceSet.Add(p.Namespace) | ||||
| 			userSet.Add(p.User) | ||||
| 		} | ||||
| 		for _, namespace := range namespaceSet.ToSlice() { | ||||
| 			dnsRoute := fmt.Sprintf("%v.%v", namespace.Name, baseDomain) | ||||
| 		for _, user := range userSet.ToSlice() { | ||||
| 			dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain) | ||||
| 			dnsConfig.Routes[dnsRoute] = nil | ||||
| 		} | ||||
| 	} else { | ||||
|  | ||||
							
								
								
									
										82
									
								
								dns_test.go
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								dns_test.go
									
									
									
									
									
								
							| @ -112,17 +112,17 @@ func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	namespaceShared1, err := app.CreateNamespace("shared1") | ||||
| 	userShared1, err := app.CreateUser("shared1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared2, err := app.CreateNamespace("shared2") | ||||
| 	userShared2, err := app.CreateUser("shared2") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared3, err := app.CreateNamespace("shared3") | ||||
| 	userShared3, err := app.CreateUser("shared3") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -131,7 +131,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared2, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared2.Name, | ||||
| 		userShared2.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -140,7 +140,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared3, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared3.Name, | ||||
| 		userShared3.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -149,7 +149,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	PreAuthKey2InShared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -157,7 +157,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, "test_get_shared_nodes_1") | ||||
| 	_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	machineInShared1 := &Machine{ | ||||
| @ -166,15 +166,15 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		DiscoKey:       "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		Hostname:       "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, machineInShared1.Hostname) | ||||
| 	_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared2 := &Machine{ | ||||
| @ -183,15 +183,15 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		UserID:    userShared2.ID, | ||||
| 		User:      *userShared2, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared2.Name, machineInShared2.Hostname) | ||||
| 	_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared3 := &Machine{ | ||||
| @ -200,15 +200,15 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		UserID:    userShared3.ID, | ||||
| 		User:      *userShared3, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared3.Name, machineInShared3.Hostname) | ||||
| 	_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine2InShared1 := &Machine{ | ||||
| @ -217,8 +217,8 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(PreAuthKey2InShared1.ID), | ||||
| @ -245,31 +245,31 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 
 | ||||
| 	c.Assert(len(dnsConfig.Routes), check.Equals, 3) | ||||
| 
 | ||||
| 	domainRouteShared1 := fmt.Sprintf("%s.%s", namespaceShared1.Name, baseDomain) | ||||
| 	domainRouteShared1 := fmt.Sprintf("%s.%s", userShared1.Name, baseDomain) | ||||
| 	_, ok := dnsConfig.Routes[domainRouteShared1] | ||||
| 	c.Assert(ok, check.Equals, true) | ||||
| 
 | ||||
| 	domainRouteShared2 := fmt.Sprintf("%s.%s", namespaceShared2.Name, baseDomain) | ||||
| 	domainRouteShared2 := fmt.Sprintf("%s.%s", userShared2.Name, baseDomain) | ||||
| 	_, ok = dnsConfig.Routes[domainRouteShared2] | ||||
| 	c.Assert(ok, check.Equals, true) | ||||
| 
 | ||||
| 	domainRouteShared3 := fmt.Sprintf("%s.%s", namespaceShared3.Name, baseDomain) | ||||
| 	domainRouteShared3 := fmt.Sprintf("%s.%s", userShared3.Name, baseDomain) | ||||
| 	_, ok = dnsConfig.Routes[domainRouteShared3] | ||||
| 	c.Assert(ok, check.Equals, true) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 	namespaceShared1, err := app.CreateNamespace("shared1") | ||||
| 	userShared1, err := app.CreateUser("shared1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared2, err := app.CreateNamespace("shared2") | ||||
| 	userShared2, err := app.CreateUser("shared2") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared3, err := app.CreateNamespace("shared3") | ||||
| 	userShared3, err := app.CreateUser("shared3") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -278,7 +278,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared2, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared2.Name, | ||||
| 		userShared2.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -287,7 +287,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyInShared3, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared3.Name, | ||||
| 		userShared3.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -296,7 +296,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKey2InShared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -304,7 +304,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, "test_get_shared_nodes_1") | ||||
| 	_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	machineInShared1 := &Machine{ | ||||
| @ -313,15 +313,15 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		DiscoKey:       "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		Hostname:       "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, machineInShared1.Hostname) | ||||
| 	_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared2 := &Machine{ | ||||
| @ -330,15 +330,15 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		UserID:    userShared2.ID, | ||||
| 		User:      *userShared2, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared2.Name, machineInShared2.Hostname) | ||||
| 	_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared3 := &Machine{ | ||||
| @ -347,15 +347,15 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		UserID:    userShared3.ID, | ||||
| 		User:      *userShared3, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared3.Name, machineInShared3.Hostname) | ||||
| 	_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine2InShared1 := &Machine{ | ||||
| @ -364,8 +364,8 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2InShared1.ID), | ||||
|  | ||||
							
								
								
									
										80
									
								
								grpcv1.go
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								grpcv1.go
									
									
									
									
									
								
							| @ -26,76 +26,76 @@ func newHeadscaleV1APIServer(h *Headscale) v1.HeadscaleServiceServer { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) GetNamespace( | ||||
| func (api headscaleV1APIServer) GetUser( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.GetNamespaceRequest, | ||||
| ) (*v1.GetNamespaceResponse, error) { | ||||
| 	namespace, err := api.h.GetNamespace(request.GetName()) | ||||
| 	request *v1.GetUserRequest, | ||||
| ) (*v1.GetUserResponse, error) { | ||||
| 	user, err := api.h.GetUser(request.GetName()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &v1.GetNamespaceResponse{Namespace: namespace.toProto()}, nil | ||||
| 	return &v1.GetUserResponse{User: user.toProto()}, nil | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) CreateNamespace( | ||||
| func (api headscaleV1APIServer) CreateUser( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.CreateNamespaceRequest, | ||||
| ) (*v1.CreateNamespaceResponse, error) { | ||||
| 	namespace, err := api.h.CreateNamespace(request.GetName()) | ||||
| 	request *v1.CreateUserRequest, | ||||
| ) (*v1.CreateUserResponse, error) { | ||||
| 	user, err := api.h.CreateUser(request.GetName()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &v1.CreateNamespaceResponse{Namespace: namespace.toProto()}, nil | ||||
| 	return &v1.CreateUserResponse{User: user.toProto()}, nil | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) RenameNamespace( | ||||
| func (api headscaleV1APIServer) RenameUser( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.RenameNamespaceRequest, | ||||
| ) (*v1.RenameNamespaceResponse, error) { | ||||
| 	err := api.h.RenameNamespace(request.GetOldName(), request.GetNewName()) | ||||
| 	request *v1.RenameUserRequest, | ||||
| ) (*v1.RenameUserResponse, error) { | ||||
| 	err := api.h.RenameUser(request.GetOldName(), request.GetNewName()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	namespace, err := api.h.GetNamespace(request.GetNewName()) | ||||
| 	user, err := api.h.GetUser(request.GetNewName()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &v1.RenameNamespaceResponse{Namespace: namespace.toProto()}, nil | ||||
| 	return &v1.RenameUserResponse{User: user.toProto()}, nil | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) DeleteNamespace( | ||||
| func (api headscaleV1APIServer) DeleteUser( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.DeleteNamespaceRequest, | ||||
| ) (*v1.DeleteNamespaceResponse, error) { | ||||
| 	err := api.h.DestroyNamespace(request.GetName()) | ||||
| 	request *v1.DeleteUserRequest, | ||||
| ) (*v1.DeleteUserResponse, error) { | ||||
| 	err := api.h.DestroyUser(request.GetName()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &v1.DeleteNamespaceResponse{}, nil | ||||
| 	return &v1.DeleteUserResponse{}, nil | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) ListNamespaces( | ||||
| func (api headscaleV1APIServer) ListUsers( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.ListNamespacesRequest, | ||||
| ) (*v1.ListNamespacesResponse, error) { | ||||
| 	namespaces, err := api.h.ListNamespaces() | ||||
| 	request *v1.ListUsersRequest, | ||||
| ) (*v1.ListUsersResponse, error) { | ||||
| 	users, err := api.h.ListUsers() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	response := make([]*v1.Namespace, len(namespaces)) | ||||
| 	for index, namespace := range namespaces { | ||||
| 		response[index] = namespace.toProto() | ||||
| 	response := make([]*v1.User, len(users)) | ||||
| 	for index, user := range users { | ||||
| 		response[index] = user.toProto() | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace().Caller().Interface("namespaces", response).Msg("") | ||||
| 	log.Trace().Caller().Interface("users", response).Msg("") | ||||
| 
 | ||||
| 	return &v1.ListNamespacesResponse{Namespaces: response}, nil | ||||
| 	return &v1.ListUsersResponse{Users: response}, nil | ||||
| } | ||||
| 
 | ||||
| func (api headscaleV1APIServer) CreatePreAuthKey( | ||||
| @ -117,7 +117,7 @@ func (api headscaleV1APIServer) CreatePreAuthKey( | ||||
| 	} | ||||
| 
 | ||||
| 	preAuthKey, err := api.h.CreatePreAuthKey( | ||||
| 		request.GetNamespace(), | ||||
| 		request.GetUser(), | ||||
| 		request.GetReusable(), | ||||
| 		request.GetEphemeral(), | ||||
| 		&expiration, | ||||
| @ -134,7 +134,7 @@ func (api headscaleV1APIServer) ExpirePreAuthKey( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.ExpirePreAuthKeyRequest, | ||||
| ) (*v1.ExpirePreAuthKeyResponse, error) { | ||||
| 	preAuthKey, err := api.h.GetPreAuthKey(request.GetNamespace(), request.Key) | ||||
| 	preAuthKey, err := api.h.GetPreAuthKey(request.GetUser(), request.Key) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -151,7 +151,7 @@ func (api headscaleV1APIServer) ListPreAuthKeys( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.ListPreAuthKeysRequest, | ||||
| ) (*v1.ListPreAuthKeysResponse, error) { | ||||
| 	preAuthKeys, err := api.h.ListPreAuthKeys(request.GetNamespace()) | ||||
| 	preAuthKeys, err := api.h.ListPreAuthKeys(request.GetUser()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -169,13 +169,13 @@ func (api headscaleV1APIServer) RegisterMachine( | ||||
| 	request *v1.RegisterMachineRequest, | ||||
| ) (*v1.RegisterMachineResponse, error) { | ||||
| 	log.Trace(). | ||||
| 		Str("namespace", request.GetNamespace()). | ||||
| 		Str("user", request.GetUser()). | ||||
| 		Str("node_key", request.GetKey()). | ||||
| 		Msg("Registering machine") | ||||
| 
 | ||||
| 	machine, err := api.h.RegisterMachineFromAuthCallback( | ||||
| 		request.GetKey(), | ||||
| 		request.GetNamespace(), | ||||
| 		request.GetUser(), | ||||
| 		nil, | ||||
| 		RegisterMethodCLI, | ||||
| 	) | ||||
| @ -313,8 +313,8 @@ func (api headscaleV1APIServer) ListMachines( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.ListMachinesRequest, | ||||
| ) (*v1.ListMachinesResponse, error) { | ||||
| 	if request.GetNamespace() != "" { | ||||
| 		machines, err := api.h.ListMachinesInNamespace(request.GetNamespace()) | ||||
| 	if request.GetUser() != "" { | ||||
| 		machines, err := api.h.ListMachinesByUser(request.GetUser()) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -357,7 +357,7 @@ func (api headscaleV1APIServer) MoveMachine( | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = api.h.SetMachineNamespace(machine, request.GetNamespace()) | ||||
| 	err = api.h.SetMachineUser(machine, request.GetUser()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -483,7 +483,7 @@ func (api headscaleV1APIServer) DebugCreateMachine( | ||||
| 	ctx context.Context, | ||||
| 	request *v1.DebugCreateMachineRequest, | ||||
| ) (*v1.DebugCreateMachineResponse, error) { | ||||
| 	namespace, err := api.h.GetNamespace(request.GetNamespace()) | ||||
| 	user, err := api.h.GetUser(request.GetUser()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -514,7 +514,7 @@ func (api headscaleV1APIServer) DebugCreateMachine( | ||||
| 		MachineKey: request.GetKey(), | ||||
| 		Hostname:   request.GetName(), | ||||
| 		GivenName:  givenName, | ||||
| 		Namespace:  *namespace, | ||||
| 		User:       *user, | ||||
| 
 | ||||
| 		Expiry:               &time.Time{}, | ||||
| 		LastSeen:             &time.Time{}, | ||||
|  | ||||
| @ -49,7 +49,7 @@ func TestOIDCAuthenticationPingAll(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	oidcConfig, err := scenario.runMockOIDC(defaultAccessTTL) | ||||
| @ -116,7 +116,7 @@ func TestOIDCExpireNodes(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	oidcConfig, err := scenario.runMockOIDC(shortAccessTTL) | ||||
| @ -169,7 +169,7 @@ func TestOIDCExpireNodes(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func (s *AuthOIDCScenario) CreateHeadscaleEnv( | ||||
| 	namespaces map[string]int, | ||||
| 	users map[string]int, | ||||
| 	opts ...hsic.Option, | ||||
| ) error { | ||||
| 	headscale, err := s.Headscale(opts...) | ||||
| @ -182,19 +182,19 @@ func (s *AuthOIDCScenario) CreateHeadscaleEnv( | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName, clientCount := range namespaces { | ||||
| 		log.Printf("creating namespace %s with %d clients", namespaceName, clientCount) | ||||
| 		err = s.CreateNamespace(namespaceName) | ||||
| 	for userName, clientCount := range users { | ||||
| 		log.Printf("creating user %s with %d clients", userName, clientCount) | ||||
| 		err = s.CreateUser(userName) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount) | ||||
| 		err = s.CreateTailscaleNodesInUser(userName, "all", clientCount) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.runTailscaleUp(namespaceName, headscale.GetEndpoint()) | ||||
| 		err = s.runTailscaleUp(userName, headscale.GetEndpoint()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @ -287,20 +287,20 @@ func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration) (*headscale.OIDC | ||||
| } | ||||
| 
 | ||||
| func (s *AuthOIDCScenario) runTailscaleUp( | ||||
| 	namespaceStr, loginServer string, | ||||
| 	userStr, loginServer string, | ||||
| ) error { | ||||
| 	headscale, err := s.Headscale() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("running tailscale up for namespace %s", namespaceStr) | ||||
| 	if namespace, ok := s.namespaces[namespaceStr]; ok { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			namespace.joinWaitGroup.Add(1) | ||||
| 	log.Printf("running tailscale up for user %s", userStr) | ||||
| 	if user, ok := s.users[userStr]; ok { | ||||
| 		for _, client := range user.Clients { | ||||
| 			user.joinWaitGroup.Add(1) | ||||
| 
 | ||||
| 			go func(c TailscaleClient) { | ||||
| 				defer namespace.joinWaitGroup.Done() | ||||
| 				defer user.joinWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(juanfont): error handle this | ||||
| 				loginURL, err := c.UpWithLoginURL(loginServer) | ||||
| @ -347,12 +347,12 @@ func (s *AuthOIDCScenario) runTailscaleUp( | ||||
| 			log.Printf("client %s is ready", client.Hostname()) | ||||
| 		} | ||||
| 
 | ||||
| 		namespace.joinWaitGroup.Wait() | ||||
| 		user.joinWaitGroup.Wait() | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoNamespaceAvailable) | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func pingAll(t *testing.T, clients []TailscaleClient, ips []netip.Addr) int { | ||||
|  | ||||
| @ -35,8 +35,8 @@ func TestAuthWebFlowAuthenticationPingAll(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"namespace2": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 		"user2": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, hsic.WithTestName("webauthping")) | ||||
| @ -93,8 +93,8 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"namespace2": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 		"user2": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, hsic.WithTestName("weblogout")) | ||||
| @ -156,8 +156,8 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { | ||||
| 		t.Errorf("failed to get headscale server: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName := range spec { | ||||
| 		err = scenario.runTailscaleUp(namespaceName, headscale.GetEndpoint()) | ||||
| 	for userName := range spec { | ||||
| 		err = scenario.runTailscaleUp(userName, headscale.GetEndpoint()) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to run tailscale up: %s", err) | ||||
| 		} | ||||
| @ -225,7 +225,7 @@ func TestAuthWebFlowLogoutAndRelogin(t *testing.T) { | ||||
| } | ||||
| 
 | ||||
| func (s *AuthWebFlowScenario) CreateHeadscaleEnv( | ||||
| 	namespaces map[string]int, | ||||
| 	users map[string]int, | ||||
| 	opts ...hsic.Option, | ||||
| ) error { | ||||
| 	headscale, err := s.Headscale(opts...) | ||||
| @ -238,19 +238,19 @@ func (s *AuthWebFlowScenario) CreateHeadscaleEnv( | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName, clientCount := range namespaces { | ||||
| 		log.Printf("creating namespace %s with %d clients", namespaceName, clientCount) | ||||
| 		err = s.CreateNamespace(namespaceName) | ||||
| 	for userName, clientCount := range users { | ||||
| 		log.Printf("creating user %s with %d clients", userName, clientCount) | ||||
| 		err = s.CreateUser(userName) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount) | ||||
| 		err = s.CreateTailscaleNodesInUser(userName, "all", clientCount) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.runTailscaleUp(namespaceName, headscale.GetEndpoint()) | ||||
| 		err = s.runTailscaleUp(userName, headscale.GetEndpoint()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @ -260,15 +260,15 @@ func (s *AuthWebFlowScenario) CreateHeadscaleEnv( | ||||
| } | ||||
| 
 | ||||
| func (s *AuthWebFlowScenario) runTailscaleUp( | ||||
| 	namespaceStr, loginServer string, | ||||
| 	userStr, loginServer string, | ||||
| ) error { | ||||
| 	log.Printf("running tailscale up for namespace %s", namespaceStr) | ||||
| 	if namespace, ok := s.namespaces[namespaceStr]; ok { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			namespace.joinWaitGroup.Add(1) | ||||
| 	log.Printf("running tailscale up for user %s", userStr) | ||||
| 	if user, ok := s.users[userStr]; ok { | ||||
| 		for _, client := range user.Clients { | ||||
| 			user.joinWaitGroup.Add(1) | ||||
| 
 | ||||
| 			go func(c TailscaleClient) { | ||||
| 				defer namespace.joinWaitGroup.Done() | ||||
| 				defer user.joinWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(juanfont): error handle this | ||||
| 				loginURL, err := c.UpWithLoginURL(loginServer) | ||||
| @ -276,7 +276,7 @@ func (s *AuthWebFlowScenario) runTailscaleUp( | ||||
| 					log.Printf("failed to run tailscale up: %s", err) | ||||
| 				} | ||||
| 
 | ||||
| 				err = s.runHeadscaleRegister(namespaceStr, loginURL) | ||||
| 				err = s.runHeadscaleRegister(userStr, loginURL) | ||||
| 				if err != nil { | ||||
| 					log.Printf("failed to register client: %s", err) | ||||
| 				} | ||||
| @ -287,15 +287,15 @@ func (s *AuthWebFlowScenario) runTailscaleUp( | ||||
| 				log.Printf("error waiting for client %s to be ready: %s", client.Hostname(), err) | ||||
| 			} | ||||
| 		} | ||||
| 		namespace.joinWaitGroup.Wait() | ||||
| 		user.joinWaitGroup.Wait() | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoNamespaceAvailable) | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *AuthWebFlowScenario) runHeadscaleRegister(namespaceStr string, loginURL *url.URL) error { | ||||
| func (s *AuthWebFlowScenario) runHeadscaleRegister(userStr string, loginURL *url.URL) error { | ||||
| 	headscale, err := s.Headscale() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @ -335,7 +335,7 @@ func (s *AuthWebFlowScenario) runHeadscaleRegister(namespaceStr string, loginURL | ||||
| 
 | ||||
| 	if headscale, err := s.Headscale(); err == nil { | ||||
| 		_, err = headscale.Execute( | ||||
| 			[]string{"headscale", "-n", namespaceStr, "nodes", "register", "--key", key}, | ||||
| 			[]string{"headscale", "-n", userStr, "nodes", "register", "--key", key}, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			log.Printf("failed to register node: %s", err) | ||||
|  | ||||
| @ -28,7 +28,7 @@ func executeAndUnmarshal[T any](headscale ControlServer, command []string, resul | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func TestNamespaceCommand(t *testing.T) { | ||||
| func TestUserCommand(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| @ -36,8 +36,8 @@ func TestNamespaceCommand(t *testing.T) { | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": 0, | ||||
| 		"namespace2": 0, | ||||
| 		"user1": 0, | ||||
| 		"user2": 0, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) | ||||
| @ -46,60 +46,60 @@ func TestNamespaceCommand(t *testing.T) { | ||||
| 	headscale, err := scenario.Headscale() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	var listNamespaces []v1.Namespace | ||||
| 	var listUsers []v1.User | ||||
| 	err = executeAndUnmarshal(headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| 		&listNamespaces, | ||||
| 		&listUsers, | ||||
| 	) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	result := []string{listNamespaces[0].Name, listNamespaces[1].Name} | ||||
| 	result := []string{listUsers[0].Name, listUsers[1].Name} | ||||
| 	sort.Strings(result) | ||||
| 
 | ||||
| 	assert.Equal( | ||||
| 		t, | ||||
| 		[]string{"namespace1", "namespace2"}, | ||||
| 		[]string{"user1", "user2"}, | ||||
| 		result, | ||||
| 	) | ||||
| 
 | ||||
| 	_, err = headscale.Execute( | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"rename", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 			"namespace2", | ||||
| 			"user2", | ||||
| 			"newname", | ||||
| 		}, | ||||
| 	) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	var listAfterRenameNamespaces []v1.Namespace | ||||
| 	var listAfterRenameUsers []v1.User | ||||
| 	err = executeAndUnmarshal(headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| 		&listAfterRenameNamespaces, | ||||
| 		&listAfterRenameUsers, | ||||
| 	) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	result = []string{listAfterRenameNamespaces[0].Name, listAfterRenameNamespaces[1].Name} | ||||
| 	result = []string{listAfterRenameUsers[0].Name, listAfterRenameUsers[1].Name} | ||||
| 	sort.Strings(result) | ||||
| 
 | ||||
| 	assert.Equal( | ||||
| 		t, | ||||
| 		[]string{"namespace1", "newname"}, | ||||
| 		[]string{"user1", "newname"}, | ||||
| 		result, | ||||
| 	) | ||||
| 
 | ||||
| @ -111,14 +111,14 @@ func TestPreAuthKeyCommand(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	namespace := "preauthkeyspace" | ||||
| 	user := "preauthkeyspace" | ||||
| 	count := 3 | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		namespace: 0, | ||||
| 		user: 0, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipak")) | ||||
| @ -137,8 +137,8 @@ func TestPreAuthKeyCommand(t *testing.T) { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"preauthkeys", | ||||
| 				"--namespace", | ||||
| 				namespace, | ||||
| 				"--user", | ||||
| 				user, | ||||
| 				"create", | ||||
| 				"--reusable", | ||||
| 				"--expiration", | ||||
| @ -163,8 +163,8 @@ func TestPreAuthKeyCommand(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -216,8 +216,8 @@ func TestPreAuthKeyCommand(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"expire", | ||||
| 			listedPreAuthKeys[1].Key, | ||||
| 		}, | ||||
| @ -230,8 +230,8 @@ func TestPreAuthKeyCommand(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -252,13 +252,13 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	namespace := "pre-auth-key-without-exp-namespace" | ||||
| 	user := "pre-auth-key-without-exp-user" | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		namespace: 0, | ||||
| 		user: 0, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipaknaexp")) | ||||
| @ -273,8 +273,8 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"create", | ||||
| 			"--reusable", | ||||
| 			"--output", | ||||
| @ -290,8 +290,8 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -317,13 +317,13 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	namespace := "pre-auth-key-reus-ephm-namespace" | ||||
| 	user := "pre-auth-key-reus-ephm-user" | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		namespace: 0, | ||||
| 		user: 0, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clipakresueeph")) | ||||
| @ -338,8 +338,8 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"create", | ||||
| 			"--reusable=true", | ||||
| 			"--output", | ||||
| @ -355,8 +355,8 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"create", | ||||
| 			"--ephemeral=true", | ||||
| 			"--output", | ||||
| @ -375,8 +375,8 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace, | ||||
| 			"--user", | ||||
| 			user, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -396,13 +396,13 @@ func TestEnablingRoutes(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	namespace := "enable-routing" | ||||
| 	user := "enable-routing" | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		namespace: 3, | ||||
| 		user: 3, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clienableroute")) | ||||
|  | ||||
| @ -10,9 +10,9 @@ type ControlServer interface { | ||||
| 	GetHealthEndpoint() string | ||||
| 	GetEndpoint() string | ||||
| 	WaitForReady() error | ||||
| 	CreateNamespace(namespace string) error | ||||
| 	CreateAuthKey(namespace string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) | ||||
| 	ListMachinesInNamespace(namespace string) ([]*v1.Machine, error) | ||||
| 	CreateUser(user string) error | ||||
| 	CreateAuthKey(user string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) | ||||
| 	ListMachinesInUser(user string) ([]*v1.Machine, error) | ||||
| 	GetCert() []byte | ||||
| 	GetHostname() string | ||||
| 	GetIP() string | ||||
|  | ||||
| @ -22,8 +22,8 @@ func TestPingAllByIP(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"namespace2": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 		"user2": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyip")) | ||||
| @ -77,8 +77,8 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"namespace2": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 		"user2": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyip")) | ||||
| @ -121,15 +121,15 @@ func TestAuthKeyLogoutAndRelogin(t *testing.T) { | ||||
| 		t.Errorf("failed to get headscale server: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName := range spec { | ||||
| 		key, err := scenario.CreatePreAuthKey(namespaceName, true, false) | ||||
| 	for userName := range spec { | ||||
| 		key, err := scenario.CreatePreAuthKey(userName, true, false) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create pre-auth key for namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to create pre-auth key for user %s: %s", userName, err) | ||||
| 		} | ||||
| 
 | ||||
| 		err = scenario.RunTailscaleUp(namespaceName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to run tailscale up for namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to run tailscale up for user %s: %s", userName, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -207,8 +207,8 @@ func TestEphemeral(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions), | ||||
| 		"namespace2": len(TailscaleVersions), | ||||
| 		"user1": len(TailscaleVersions), | ||||
| 		"user2": len(TailscaleVersions), | ||||
| 	} | ||||
| 
 | ||||
| 	headscale, err := scenario.Headscale(hsic.WithTestName("ephemeral")) | ||||
| @ -216,25 +216,25 @@ func TestEphemeral(t *testing.T) { | ||||
| 		t.Errorf("failed to create headscale environment: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName, clientCount := range spec { | ||||
| 		err = scenario.CreateNamespace(namespaceName) | ||||
| 	for userName, clientCount := range spec { | ||||
| 		err = scenario.CreateUser(userName) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to create user %s: %s", userName, err) | ||||
| 		} | ||||
| 
 | ||||
| 		err = scenario.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount, []tsic.Option{}...) | ||||
| 		err = scenario.CreateTailscaleNodesInUser(userName, "all", clientCount, []tsic.Option{}...) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create tailscale nodes in namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to create tailscale nodes in user %s: %s", userName, err) | ||||
| 		} | ||||
| 
 | ||||
| 		key, err := scenario.CreatePreAuthKey(namespaceName, true, true) | ||||
| 		key, err := scenario.CreatePreAuthKey(userName, true, true) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create pre-auth key for namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to create pre-auth key for user %s: %s", userName, err) | ||||
| 		} | ||||
| 
 | ||||
| 		err = scenario.RunTailscaleUp(namespaceName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to run tailscale up for namespace %s: %s", namespaceName, err) | ||||
| 			t.Errorf("failed to run tailscale up for user %s: %s", userName, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -278,19 +278,19 @@ func TestEphemeral(t *testing.T) { | ||||
| 
 | ||||
| 	t.Logf("all clients logged out") | ||||
| 
 | ||||
| 	for namespaceName := range spec { | ||||
| 		machines, err := headscale.ListMachinesInNamespace(namespaceName) | ||||
| 	for userName := range spec { | ||||
| 		machines, err := headscale.ListMachinesInUser(userName) | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Err(err). | ||||
| 				Str("namespace", namespaceName). | ||||
| 				Msg("Error listing machines in namespace") | ||||
| 				Str("user", userName). | ||||
| 				Msg("Error listing machines in user") | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if len(machines) != 0 { | ||||
| 			t.Errorf("expected no machines, got %d in namespace %s", len(machines), namespaceName) | ||||
| 			t.Errorf("expected no machines, got %d in user %s", len(machines), userName) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -311,8 +311,8 @@ func TestPingAllByHostname(t *testing.T) { | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		// Omit 1.16.2 (-1) because it does not have the FQDN field | ||||
| 		"namespace3": len(TailscaleVersions) - 1, | ||||
| 		"namespace4": len(TailscaleVersions) - 1, | ||||
| 		"user3": len(TailscaleVersions) - 1, | ||||
| 		"user4": len(TailscaleVersions) - 1, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyname")) | ||||
|  | ||||
| @ -328,10 +328,10 @@ func (t *HeadscaleInContainer) WaitForReady() error { | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (t *HeadscaleInContainer) CreateNamespace( | ||||
| 	namespace string, | ||||
| func (t *HeadscaleInContainer) CreateUser( | ||||
| 	user string, | ||||
| ) error { | ||||
| 	command := []string{"headscale", "namespaces", "create", namespace} | ||||
| 	command := []string{"headscale", "users", "create", user} | ||||
| 
 | ||||
| 	_, _, err := dockertestutil.ExecuteCommand( | ||||
| 		t.container, | ||||
| @ -346,14 +346,14 @@ func (t *HeadscaleInContainer) CreateNamespace( | ||||
| } | ||||
| 
 | ||||
| func (t *HeadscaleInContainer) CreateAuthKey( | ||||
| 	namespace string, | ||||
| 	user string, | ||||
| 	reusable bool, | ||||
| 	ephemeral bool, | ||||
| ) (*v1.PreAuthKey, error) { | ||||
| 	command := []string{ | ||||
| 		"headscale", | ||||
| 		"--namespace", | ||||
| 		namespace, | ||||
| 		"--user", | ||||
| 		user, | ||||
| 		"preauthkeys", | ||||
| 		"create", | ||||
| 		"--expiration", | ||||
| @ -388,10 +388,10 @@ func (t *HeadscaleInContainer) CreateAuthKey( | ||||
| 	return &preAuthKey, nil | ||||
| } | ||||
| 
 | ||||
| func (t *HeadscaleInContainer) ListMachinesInNamespace( | ||||
| 	namespace string, | ||||
| func (t *HeadscaleInContainer) ListMachinesInUser( | ||||
| 	user string, | ||||
| ) ([]*v1.Machine, error) { | ||||
| 	command := []string{"headscale", "--namespace", namespace, "nodes", "list", "--output", "json"} | ||||
| 	command := []string{"headscale", "--user", user, "nodes", "list", "--output", "json"} | ||||
| 
 | ||||
| 	result, _, err := dockertestutil.ExecuteCommand( | ||||
| 		t.container, | ||||
|  | ||||
| @ -25,7 +25,7 @@ const ( | ||||
| 
 | ||||
| var ( | ||||
| 	errNoHeadscaleAvailable = errors.New("no headscale available") | ||||
| 	errNoNamespaceAvailable = errors.New("no namespace available") | ||||
| 	errNoUserAvailable = errors.New("no user available") | ||||
| 
 | ||||
| 	// Tailscale started adding TS2021 support in CapabilityVersion>=28 (v1.24.0), but | ||||
| 	// proper support in Headscale was only added for CapabilityVersion>=39 clients (v1.30.0). | ||||
| @ -61,7 +61,7 @@ var ( | ||||
| 	) | ||||
| ) | ||||
| 
 | ||||
| type Namespace struct { | ||||
| type User struct { | ||||
| 	Clients map[string]TailscaleClient | ||||
| 
 | ||||
| 	createWaitGroup sync.WaitGroup | ||||
| @ -75,7 +75,7 @@ type Scenario struct { | ||||
| 	// use one. | ||||
| 	controlServers *xsync.MapOf[string, ControlServer] | ||||
| 
 | ||||
| 	namespaces map[string]*Namespace | ||||
| 	users map[string]*User | ||||
| 
 | ||||
| 	pool    *dockertest.Pool | ||||
| 	network *dockertest.Network | ||||
| @ -116,7 +116,7 @@ func NewScenario() (*Scenario, error) { | ||||
| 
 | ||||
| 	return &Scenario{ | ||||
| 		controlServers: xsync.NewMapOf[ControlServer](), | ||||
| 		namespaces:     make(map[string]*Namespace), | ||||
| 		users:     make(map[string]*User), | ||||
| 
 | ||||
| 		pool:    pool, | ||||
| 		network: network, | ||||
| @ -136,9 +136,9 @@ func (s *Scenario) Shutdown() error { | ||||
| 		return true | ||||
| 	}) | ||||
| 
 | ||||
| 	for namespaceName, namespace := range s.namespaces { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			log.Printf("removing client %s in namespace %s", client.Hostname(), namespaceName) | ||||
| 	for userName, user := range s.users { | ||||
| 		for _, client := range user.Clients { | ||||
| 			log.Printf("removing client %s in user %s", client.Hostname(), userName) | ||||
| 			err := client.Shutdown() | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to tear down client: %w", err) | ||||
| @ -158,13 +158,13 @@ func (s *Scenario) Shutdown() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) Namespaces() []string { | ||||
| 	namespaces := make([]string, 0) | ||||
| 	for namespace := range s.namespaces { | ||||
| 		namespaces = append(namespaces, namespace) | ||||
| func (s *Scenario) Users() []string { | ||||
| 	users := make([]string, 0) | ||||
| 	for user := range s.users { | ||||
| 		users = append(users, user) | ||||
| 	} | ||||
| 
 | ||||
| 	return namespaces | ||||
| 	return users | ||||
| } | ||||
| 
 | ||||
| /// Headscale related stuff | ||||
| @ -194,45 +194,45 @@ func (s *Scenario) Headscale(opts ...hsic.Option) (ControlServer, error) { | ||||
| 	return headscale, nil | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) CreatePreAuthKey(namespace string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) { | ||||
| func (s *Scenario) CreatePreAuthKey(user string, reusable bool, ephemeral bool) (*v1.PreAuthKey, error) { | ||||
| 	if headscale, err := s.Headscale(); err == nil { | ||||
| 		key, err := headscale.CreateAuthKey(namespace, reusable, ephemeral) | ||||
| 		key, err := headscale.CreateAuthKey(user, reusable, ephemeral) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed to create namespace: %w", err) | ||||
| 			return nil, fmt.Errorf("failed to create user: %w", err) | ||||
| 		} | ||||
| 
 | ||||
| 		return key, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, fmt.Errorf("failed to create namespace: %w", errNoHeadscaleAvailable) | ||||
| 	return nil, fmt.Errorf("failed to create user: %w", errNoHeadscaleAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) CreateNamespace(namespace string) error { | ||||
| func (s *Scenario) CreateUser(user string) error { | ||||
| 	if headscale, err := s.Headscale(); err == nil { | ||||
| 		err := headscale.CreateNamespace(namespace) | ||||
| 		err := headscale.CreateUser(user) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to create namespace: %w", err) | ||||
| 			return fmt.Errorf("failed to create user: %w", err) | ||||
| 		} | ||||
| 
 | ||||
| 		s.namespaces[namespace] = &Namespace{ | ||||
| 		s.users[user] = &User{ | ||||
| 			Clients: make(map[string]TailscaleClient), | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("failed to create namespace: %w", errNoHeadscaleAvailable) | ||||
| 	return fmt.Errorf("failed to create user: %w", errNoHeadscaleAvailable) | ||||
| } | ||||
| 
 | ||||
| /// Client related stuff | ||||
| 
 | ||||
| func (s *Scenario) CreateTailscaleNodesInNamespace( | ||||
| 	namespaceStr string, | ||||
| func (s *Scenario) CreateTailscaleNodesInUser( | ||||
| 	userStr string, | ||||
| 	requestedVersion string, | ||||
| 	count int, | ||||
| 	opts ...tsic.Option, | ||||
| ) error { | ||||
| 	if namespace, ok := s.namespaces[namespaceStr]; ok { | ||||
| 	if user, ok := s.users[userStr]; ok { | ||||
| 		for i := 0; i < count; i++ { | ||||
| 			version := requestedVersion | ||||
| 			if requestedVersion == "all" { | ||||
| @ -247,7 +247,7 @@ func (s *Scenario) CreateTailscaleNodesInNamespace( | ||||
| 			cert := headscale.GetCert() | ||||
| 			hostname := headscale.GetHostname() | ||||
| 
 | ||||
| 			namespace.createWaitGroup.Add(1) | ||||
| 			user.createWaitGroup.Add(1) | ||||
| 
 | ||||
| 			opts = append(opts, | ||||
| 				tsic.WithHeadscaleTLS(cert), | ||||
| @ -255,7 +255,7 @@ func (s *Scenario) CreateTailscaleNodesInNamespace( | ||||
| 			) | ||||
| 
 | ||||
| 			go func() { | ||||
| 				defer namespace.createWaitGroup.Done() | ||||
| 				defer user.createWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(kradalby): error handle this | ||||
| 				tsClient, err := tsic.New( | ||||
| @ -275,26 +275,26 @@ func (s *Scenario) CreateTailscaleNodesInNamespace( | ||||
| 					log.Printf("failed to wait for tailscaled: %s", err) | ||||
| 				} | ||||
| 
 | ||||
| 				namespace.Clients[tsClient.Hostname()] = tsClient | ||||
| 				user.Clients[tsClient.Hostname()] = tsClient | ||||
| 			}() | ||||
| 		} | ||||
| 		namespace.createWaitGroup.Wait() | ||||
| 		user.createWaitGroup.Wait() | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("failed to add tailscale node: %w", errNoNamespaceAvailable) | ||||
| 	return fmt.Errorf("failed to add tailscale node: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) RunTailscaleUp( | ||||
| 	namespaceStr, loginServer, authKey string, | ||||
| 	userStr, loginServer, authKey string, | ||||
| ) error { | ||||
| 	if namespace, ok := s.namespaces[namespaceStr]; ok { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			namespace.joinWaitGroup.Add(1) | ||||
| 	if user, ok := s.users[userStr]; ok { | ||||
| 		for _, client := range user.Clients { | ||||
| 			user.joinWaitGroup.Add(1) | ||||
| 
 | ||||
| 			go func(c TailscaleClient) { | ||||
| 				defer namespace.joinWaitGroup.Done() | ||||
| 				defer user.joinWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(kradalby): error handle this | ||||
| 				_ = c.Up(loginServer, authKey) | ||||
| @ -306,19 +306,19 @@ func (s *Scenario) RunTailscaleUp( | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		namespace.joinWaitGroup.Wait() | ||||
| 		user.joinWaitGroup.Wait() | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoNamespaceAvailable) | ||||
| 	return fmt.Errorf("failed to up tailscale node: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) CountTailscale() int { | ||||
| 	count := 0 | ||||
| 
 | ||||
| 	for _, namespace := range s.namespaces { | ||||
| 		count += len(namespace.Clients) | ||||
| 	for _, user := range s.users { | ||||
| 		count += len(user.Clients) | ||||
| 	} | ||||
| 
 | ||||
| 	return count | ||||
| @ -327,18 +327,18 @@ func (s *Scenario) CountTailscale() int { | ||||
| func (s *Scenario) WaitForTailscaleSync() error { | ||||
| 	tsCount := s.CountTailscale() | ||||
| 
 | ||||
| 	for _, namespace := range s.namespaces { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			namespace.syncWaitGroup.Add(1) | ||||
| 	for _, user := range s.users { | ||||
| 		for _, client := range user.Clients { | ||||
| 			user.syncWaitGroup.Add(1) | ||||
| 
 | ||||
| 			go func(c TailscaleClient) { | ||||
| 				defer namespace.syncWaitGroup.Done() | ||||
| 				defer user.syncWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(kradalby): error handle this | ||||
| 				_ = c.WaitForPeers(tsCount) | ||||
| 			}(client) | ||||
| 		} | ||||
| 		namespace.syncWaitGroup.Wait() | ||||
| 		user.syncWaitGroup.Wait() | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| @ -346,9 +346,9 @@ func (s *Scenario) WaitForTailscaleSync() error { | ||||
| 
 | ||||
| // CreateHeadscaleEnv is a conventient method returning a set up Headcale | ||||
| // test environment with nodes of all versions, joined to the server with X | ||||
| // namespaces. | ||||
| // users. | ||||
| func (s *Scenario) CreateHeadscaleEnv( | ||||
| 	namespaces map[string]int, | ||||
| 	users map[string]int, | ||||
| 	tsOpts []tsic.Option, | ||||
| 	opts ...hsic.Option, | ||||
| ) error { | ||||
| @ -357,23 +357,23 @@ func (s *Scenario) CreateHeadscaleEnv( | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for namespaceName, clientCount := range namespaces { | ||||
| 		err = s.CreateNamespace(namespaceName) | ||||
| 	for userName, clientCount := range users { | ||||
| 		err = s.CreateUser(userName) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.CreateTailscaleNodesInNamespace(namespaceName, "all", clientCount, tsOpts...) | ||||
| 		err = s.CreateTailscaleNodesInUser(userName, "all", clientCount, tsOpts...) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		key, err := s.CreatePreAuthKey(namespaceName, true, false) | ||||
| 		key, err := s.CreatePreAuthKey(userName, true, false) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = s.RunTailscaleUp(namespaceName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		err = s.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey()) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @ -382,9 +382,9 @@ func (s *Scenario) CreateHeadscaleEnv( | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) GetIPs(namespace string) ([]netip.Addr, error) { | ||||
| func (s *Scenario) GetIPs(user string) ([]netip.Addr, error) { | ||||
| 	var ips []netip.Addr | ||||
| 	if ns, ok := s.namespaces[namespace]; ok { | ||||
| 	if ns, ok := s.users[user]; ok { | ||||
| 		for _, client := range ns.Clients { | ||||
| 			clientIps, err := client.IPs() | ||||
| 			if err != nil { | ||||
| @ -396,12 +396,12 @@ func (s *Scenario) GetIPs(namespace string) ([]netip.Addr, error) { | ||||
| 		return ips, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return ips, fmt.Errorf("failed to get ips: %w", errNoNamespaceAvailable) | ||||
| 	return ips, fmt.Errorf("failed to get ips: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) GetClients(namespace string) ([]TailscaleClient, error) { | ||||
| func (s *Scenario) GetClients(user string) ([]TailscaleClient, error) { | ||||
| 	var clients []TailscaleClient | ||||
| 	if ns, ok := s.namespaces[namespace]; ok { | ||||
| 	if ns, ok := s.users[user]; ok { | ||||
| 		for _, client := range ns.Clients { | ||||
| 			clients = append(clients, client) | ||||
| 		} | ||||
| @ -409,18 +409,18 @@ func (s *Scenario) GetClients(namespace string) ([]TailscaleClient, error) { | ||||
| 		return clients, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return clients, fmt.Errorf("failed to get clients: %w", errNoNamespaceAvailable) | ||||
| 	return clients, fmt.Errorf("failed to get clients: %w", errNoUserAvailable) | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) ListTailscaleClients(namespaces ...string) ([]TailscaleClient, error) { | ||||
| func (s *Scenario) ListTailscaleClients(users ...string) ([]TailscaleClient, error) { | ||||
| 	var allClients []TailscaleClient | ||||
| 
 | ||||
| 	if len(namespaces) == 0 { | ||||
| 		namespaces = s.Namespaces() | ||||
| 	if len(users) == 0 { | ||||
| 		users = s.Users() | ||||
| 	} | ||||
| 
 | ||||
| 	for _, namespace := range namespaces { | ||||
| 		clients, err := s.GetClients(namespace) | ||||
| 	for _, user := range users { | ||||
| 		clients, err := s.GetClients(user) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -431,15 +431,15 @@ func (s *Scenario) ListTailscaleClients(namespaces ...string) ([]TailscaleClient | ||||
| 	return allClients, nil | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) ListTailscaleClientsIPs(namespaces ...string) ([]netip.Addr, error) { | ||||
| func (s *Scenario) ListTailscaleClientsIPs(users ...string) ([]netip.Addr, error) { | ||||
| 	var allIps []netip.Addr | ||||
| 
 | ||||
| 	if len(namespaces) == 0 { | ||||
| 		namespaces = s.Namespaces() | ||||
| 	if len(users) == 0 { | ||||
| 		users = s.Users() | ||||
| 	} | ||||
| 
 | ||||
| 	for _, namespace := range namespaces { | ||||
| 		ips, err := s.GetIPs(namespace) | ||||
| 	for _, user := range users { | ||||
| 		ips, err := s.GetIPs(user) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @ -450,10 +450,10 @@ func (s *Scenario) ListTailscaleClientsIPs(namespaces ...string) ([]netip.Addr, | ||||
| 	return allIps, nil | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) ListTailscaleClientsFQDNs(namespaces ...string) ([]string, error) { | ||||
| func (s *Scenario) ListTailscaleClientsFQDNs(users ...string) ([]string, error) { | ||||
| 	allFQDNs := make([]string, 0) | ||||
| 
 | ||||
| 	clients, err := s.ListTailscaleClients(namespaces...) | ||||
| 	clients, err := s.ListTailscaleClients(users...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -471,17 +471,17 @@ func (s *Scenario) ListTailscaleClientsFQDNs(namespaces ...string) ([]string, er | ||||
| } | ||||
| 
 | ||||
| func (s *Scenario) WaitForTailscaleLogout() { | ||||
| 	for _, namespace := range s.namespaces { | ||||
| 		for _, client := range namespace.Clients { | ||||
| 			namespace.syncWaitGroup.Add(1) | ||||
| 	for _, user := range s.users { | ||||
| 		for _, client := range user.Clients { | ||||
| 			user.syncWaitGroup.Add(1) | ||||
| 
 | ||||
| 			go func(c TailscaleClient) { | ||||
| 				defer namespace.syncWaitGroup.Done() | ||||
| 				defer user.syncWaitGroup.Done() | ||||
| 
 | ||||
| 				// TODO(kradalby): error handle this | ||||
| 				_ = c.WaitForLogout() | ||||
| 			}(client) | ||||
| 		} | ||||
| 		namespace.syncWaitGroup.Wait() | ||||
| 		user.syncWaitGroup.Wait() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -31,7 +31,7 @@ func TestHeadscale(t *testing.T) { | ||||
| 
 | ||||
| 	var err error | ||||
| 
 | ||||
| 	namespace := "test-space" | ||||
| 	user := "test-space" | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	if err != nil { | ||||
| @ -50,19 +50,19 @@ func TestHeadscale(t *testing.T) { | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("create-namespace", func(t *testing.T) { | ||||
| 		err := scenario.CreateNamespace(namespace) | ||||
| 	t.Run("create-user", func(t *testing.T) { | ||||
| 		err := scenario.CreateUser(user) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create namespace: %s", err) | ||||
| 			t.Errorf("failed to create user: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if _, ok := scenario.namespaces[namespace]; !ok { | ||||
| 			t.Errorf("namespace is not in scenario") | ||||
| 		if _, ok := scenario.users[user]; !ok { | ||||
| 			t.Errorf("user is not in scenario") | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("create-auth-key", func(t *testing.T) { | ||||
| 		_, err := scenario.CreatePreAuthKey(namespace, true, false) | ||||
| 		_, err := scenario.CreatePreAuthKey(user, true, false) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create preauthkey: %s", err) | ||||
| 		} | ||||
| @ -82,24 +82,24 @@ func TestCreateTailscale(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| 	namespace := "only-create-containers" | ||||
| 	user := "only-create-containers" | ||||
| 
 | ||||
| 	scenario, err := NewScenario() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("failed to create scenario: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	scenario.namespaces[namespace] = &Namespace{ | ||||
| 	scenario.users[user] = &User{ | ||||
| 		Clients: make(map[string]TailscaleClient), | ||||
| 	} | ||||
| 
 | ||||
| 	t.Run("create-tailscale", func(t *testing.T) { | ||||
| 		err := scenario.CreateTailscaleNodesInNamespace(namespace, "all", 3) | ||||
| 		err := scenario.CreateTailscaleNodesInUser(user, "all", 3) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to add tailscale nodes: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if clients := len(scenario.namespaces[namespace].Clients); clients != 3 { | ||||
| 		if clients := len(scenario.users[user].Clients); clients != 3 { | ||||
| 			t.Errorf("wrong number of tailscale clients: %d != %d", clients, 3) | ||||
| 		} | ||||
| 
 | ||||
| @ -122,7 +122,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { | ||||
| 
 | ||||
| 	var err error | ||||
| 
 | ||||
| 	namespace := "join-node-test" | ||||
| 	user := "join-node-test" | ||||
| 
 | ||||
| 	count := 1 | ||||
| 
 | ||||
| @ -143,30 +143,30 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("create-namespace", func(t *testing.T) { | ||||
| 		err := scenario.CreateNamespace(namespace) | ||||
| 	t.Run("create-user", func(t *testing.T) { | ||||
| 		err := scenario.CreateUser(user) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create namespace: %s", err) | ||||
| 			t.Errorf("failed to create user: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if _, ok := scenario.namespaces[namespace]; !ok { | ||||
| 			t.Errorf("namespace is not in scenario") | ||||
| 		if _, ok := scenario.users[user]; !ok { | ||||
| 			t.Errorf("user is not in scenario") | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("create-tailscale", func(t *testing.T) { | ||||
| 		err := scenario.CreateTailscaleNodesInNamespace(namespace, "1.30.2", count) | ||||
| 		err := scenario.CreateTailscaleNodesInUser(user, "1.30.2", count) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to add tailscale nodes: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if clients := len(scenario.namespaces[namespace].Clients); clients != count { | ||||
| 		if clients := len(scenario.users[user].Clients); clients != count { | ||||
| 			t.Errorf("wrong number of tailscale clients: %d != %d", clients, count) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("join-headscale", func(t *testing.T) { | ||||
| 		key, err := scenario.CreatePreAuthKey(namespace, true, false) | ||||
| 		key, err := scenario.CreatePreAuthKey(user, true, false) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to create preauthkey: %s", err) | ||||
| 		} | ||||
| @ -177,7 +177,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { | ||||
| 		} | ||||
| 
 | ||||
| 		err = scenario.RunTailscaleUp( | ||||
| 			namespace, | ||||
| 			user, | ||||
| 			headscale.GetEndpoint(), | ||||
| 			key.GetKey(), | ||||
| 		) | ||||
| @ -187,7 +187,7 @@ func TestTailscaleNodesJoiningHeadcale(t *testing.T) { | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("get-ips", func(t *testing.T) { | ||||
| 		ips, err := scenario.GetIPs(namespace) | ||||
| 		ips, err := scenario.GetIPs(user) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("failed to get tailscale ips: %s", err) | ||||
| 		} | ||||
|  | ||||
| @ -41,7 +41,7 @@ var retry = func(times int, sleepInterval time.Duration, | ||||
| 	return result, stderr, err | ||||
| } | ||||
| 
 | ||||
| func TestSSHOneNamespaceAllToAll(t *testing.T) { | ||||
| func TestSSHOneUserAllToAll(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| @ -51,7 +51,7 @@ func TestSSHOneNamespaceAllToAll(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions) - 5, | ||||
| 		"user1": len(TailscaleVersions) - 5, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, | ||||
| @ -59,7 +59,7 @@ func TestSSHOneNamespaceAllToAll(t *testing.T) { | ||||
| 		hsic.WithACLPolicy( | ||||
| 			&headscale.ACLPolicy{ | ||||
| 				Groups: map[string][]string{ | ||||
| 					"group:integration-test": {"namespace1"}, | ||||
| 					"group:integration-test": {"user1"}, | ||||
| 				}, | ||||
| 				ACLs: []headscale.ACL{ | ||||
| 					{ | ||||
| @ -117,7 +117,7 @@ func TestSSHOneNamespaceAllToAll(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| func TestSSHMultipleUsersAllToAll(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| @ -127,8 +127,8 @@ func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions) - 5, | ||||
| 		"namespace2": len(TailscaleVersions) - 5, | ||||
| 		"user1": len(TailscaleVersions) - 5, | ||||
| 		"user2": len(TailscaleVersions) - 5, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, | ||||
| @ -136,7 +136,7 @@ func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| 		hsic.WithACLPolicy( | ||||
| 			&headscale.ACLPolicy{ | ||||
| 				Groups: map[string][]string{ | ||||
| 					"group:integration-test": {"namespace1", "namespace2"}, | ||||
| 					"group:integration-test": {"user1", "user2"}, | ||||
| 				}, | ||||
| 				ACLs: []headscale.ACL{ | ||||
| 					{ | ||||
| @ -163,12 +163,12 @@ func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| 		t.Errorf("failed to create headscale environment: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	nsOneClients, err := scenario.ListTailscaleClients("namespace1") | ||||
| 	nsOneClients, err := scenario.ListTailscaleClients("user1") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("failed to get clients: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	nsTwoClients, err := scenario.ListTailscaleClients("namespace2") | ||||
| 	nsTwoClients, err := scenario.ListTailscaleClients("user2") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("failed to get clients: %s", err) | ||||
| 	} | ||||
| @ -183,7 +183,7 @@ func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| 		t.Errorf("failed to get FQDNs: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	testInterNamespaceSSH := func(sourceClients []TailscaleClient, targetClients []TailscaleClient) { | ||||
| 	testInterUserSSH := func(sourceClients []TailscaleClient, targetClients []TailscaleClient) { | ||||
| 		for _, client := range sourceClients { | ||||
| 			for _, peer := range targetClients { | ||||
| 				assertSSHHostname(t, client, peer) | ||||
| @ -191,8 +191,8 @@ func TestSSHMultipleNamespacesAllToAll(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	testInterNamespaceSSH(nsOneClients, nsTwoClients) | ||||
| 	testInterNamespaceSSH(nsTwoClients, nsOneClients) | ||||
| 	testInterUserSSH(nsOneClients, nsTwoClients) | ||||
| 	testInterUserSSH(nsTwoClients, nsOneClients) | ||||
| 
 | ||||
| 	err = scenario.Shutdown() | ||||
| 	if err != nil { | ||||
| @ -210,7 +210,7 @@ func TestSSHNoSSHConfigured(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions) - 5, | ||||
| 		"user1": len(TailscaleVersions) - 5, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, | ||||
| @ -218,7 +218,7 @@ func TestSSHNoSSHConfigured(t *testing.T) { | ||||
| 		hsic.WithACLPolicy( | ||||
| 			&headscale.ACLPolicy{ | ||||
| 				Groups: map[string][]string{ | ||||
| 					"group:integration-test": {"namespace1"}, | ||||
| 					"group:integration-test": {"user1"}, | ||||
| 				}, | ||||
| 				ACLs: []headscale.ACL{ | ||||
| 					{ | ||||
| @ -280,7 +280,7 @@ func TestSSHIsBlockedInACL(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespace1": len(TailscaleVersions) - 5, | ||||
| 		"user1": len(TailscaleVersions) - 5, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, | ||||
| @ -288,7 +288,7 @@ func TestSSHIsBlockedInACL(t *testing.T) { | ||||
| 		hsic.WithACLPolicy( | ||||
| 			&headscale.ACLPolicy{ | ||||
| 				Groups: map[string][]string{ | ||||
| 					"group:integration-test": {"namespace1"}, | ||||
| 					"group:integration-test": {"user1"}, | ||||
| 				}, | ||||
| 				ACLs: []headscale.ACL{ | ||||
| 					{ | ||||
| @ -347,7 +347,7 @@ func TestSSHIsBlockedInACL(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestSSNamespaceOnlyIsolation(t *testing.T) { | ||||
| func TestSSUserOnlyIsolation(t *testing.T) { | ||||
| 	IntegrationSkip(t) | ||||
| 	t.Parallel() | ||||
| 
 | ||||
| @ -357,8 +357,8 @@ func TestSSNamespaceOnlyIsolation(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	spec := map[string]int{ | ||||
| 		"namespaceacl1": len(TailscaleVersions) - 5, | ||||
| 		"namespaceacl2": len(TailscaleVersions) - 5, | ||||
| 		"useracl1": len(TailscaleVersions) - 5, | ||||
| 		"useracl2": len(TailscaleVersions) - 5, | ||||
| 	} | ||||
| 
 | ||||
| 	err = scenario.CreateHeadscaleEnv(spec, | ||||
| @ -366,8 +366,8 @@ func TestSSNamespaceOnlyIsolation(t *testing.T) { | ||||
| 		hsic.WithACLPolicy( | ||||
| 			&headscale.ACLPolicy{ | ||||
| 				Groups: map[string][]string{ | ||||
| 					"group:ssh1": {"namespaceacl1"}, | ||||
| 					"group:ssh2": {"namespaceacl2"}, | ||||
| 					"group:ssh1": {"useracl1"}, | ||||
| 					"group:ssh2": {"useracl2"}, | ||||
| 				}, | ||||
| 				ACLs: []headscale.ACL{ | ||||
| 					{ | ||||
| @ -392,7 +392,7 @@ func TestSSNamespaceOnlyIsolation(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		), | ||||
| 		hsic.WithTestName("sshtwonamespaceaclblock"), | ||||
| 		hsic.WithTestName("sshtwouseraclblock"), | ||||
| 		hsic.WithConfigEnv(map[string]string{ | ||||
| 			"HEADSCALE_EXPERIMENTAL_FEATURE_SSH": "1", | ||||
| 		}), | ||||
| @ -401,12 +401,12 @@ func TestSSNamespaceOnlyIsolation(t *testing.T) { | ||||
| 		t.Errorf("failed to create headscale environment: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	ssh1Clients, err := scenario.ListTailscaleClients("namespaceacl1") | ||||
| 	ssh1Clients, err := scenario.ListTailscaleClients("useracl1") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("failed to get clients: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	ssh2Clients, err := scenario.ListTailscaleClients("namespaceacl2") | ||||
| 	ssh2Clients, err := scenario.ListTailscaleClients("useracl2") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("failed to get clients: %s", err) | ||||
| 	} | ||||
|  | ||||
| @ -138,12 +138,12 @@ func (s *IntegrationCLITestSuite) HandleStats( | ||||
| 	s.stats = stats | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) createNamespace(name string) (*v1.Namespace, error) { | ||||
| func (s *IntegrationCLITestSuite) createUser(name string) (*v1.User, error) { | ||||
| 	result, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"create", | ||||
| 			name, | ||||
| 			"--output", | ||||
| @ -155,38 +155,38 @@ func (s *IntegrationCLITestSuite) createNamespace(name string) (*v1.Namespace, e | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var namespace v1.Namespace | ||||
| 	err = json.Unmarshal([]byte(result), &namespace) | ||||
| 	var user v1.User | ||||
| 	err = json.Unmarshal([]byte(result), &user) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &namespace, nil | ||||
| 	return &user, nil | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNamespaceCommand() { | ||||
| 	names := []string{"namespace1", "otherspace", "tasty"} | ||||
| 	namespaces := make([]*v1.Namespace, len(names)) | ||||
| func (s *IntegrationCLITestSuite) TestUserCommand() { | ||||
| 	names := []string{"user1", "otherspace", "tasty"} | ||||
| 	users := make([]*v1.User, len(names)) | ||||
| 
 | ||||
| 	for index, namespaceName := range names { | ||||
| 		namespace, err := s.createNamespace(namespaceName) | ||||
| 	for index, userName := range names { | ||||
| 		user, err := s.createUser(userName) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 		namespaces[index] = namespace | ||||
| 		users[index] = user | ||||
| 	} | ||||
| 
 | ||||
| 	assert.Len(s.T(), namespaces, len(names)) | ||||
| 	assert.Len(s.T(), users, len(names)) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), names[0], namespaces[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], namespaces[1].Name) | ||||
| 	assert.Equal(s.T(), names[2], namespaces[2].Name) | ||||
| 	assert.Equal(s.T(), names[0], users[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], users[1].Name) | ||||
| 	assert.Equal(s.T(), names[2], users[2].Name) | ||||
| 
 | ||||
| 	// Test list namespaces | ||||
| 	// Test list users | ||||
| 	listResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -195,20 +195,20 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var listedNamespaces []v1.Namespace | ||||
| 	err = json.Unmarshal([]byte(listResult), &listedNamespaces) | ||||
| 	var listedUsers []v1.User | ||||
| 	err = json.Unmarshal([]byte(listResult), &listedUsers) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), names[0], listedNamespaces[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], listedNamespaces[1].Name) | ||||
| 	assert.Equal(s.T(), names[2], listedNamespaces[2].Name) | ||||
| 	assert.Equal(s.T(), names[0], listedUsers[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], listedUsers[1].Name) | ||||
| 	assert.Equal(s.T(), names[2], listedUsers[2].Name) | ||||
| 
 | ||||
| 	// Test rename namespace | ||||
| 	// Test rename user | ||||
| 	renameResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"rename", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -219,18 +219,18 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var renamedNamespace v1.Namespace | ||||
| 	err = json.Unmarshal([]byte(renameResult), &renamedNamespace) | ||||
| 	var renamedUser v1.User | ||||
| 	err = json.Unmarshal([]byte(renameResult), &renamedUser) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), renamedNamespace.Name, "newname") | ||||
| 	assert.Equal(s.T(), renamedUser.Name, "newname") | ||||
| 
 | ||||
| 	// Test list after rename namespaces | ||||
| 	// Test list after rename users | ||||
| 	listAfterRenameResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"namespaces", | ||||
| 			"users", | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -239,19 +239,19 @@ func (s *IntegrationCLITestSuite) TestNamespaceCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var listedAfterRenameNamespaces []v1.Namespace | ||||
| 	err = json.Unmarshal([]byte(listAfterRenameResult), &listedAfterRenameNamespaces) | ||||
| 	var listedAfterRenameUsers []v1.User | ||||
| 	err = json.Unmarshal([]byte(listAfterRenameResult), &listedAfterRenameUsers) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), names[0], listedAfterRenameNamespaces[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], listedAfterRenameNamespaces[1].Name) | ||||
| 	assert.Equal(s.T(), "newname", listedAfterRenameNamespaces[2].Name) | ||||
| 	assert.Equal(s.T(), names[0], listedAfterRenameUsers[0].Name) | ||||
| 	assert.Equal(s.T(), names[1], listedAfterRenameUsers[1].Name) | ||||
| 	assert.Equal(s.T(), "newname", listedAfterRenameUsers[2].Name) | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| 	count := 5 | ||||
| 
 | ||||
| 	namespace, err := s.createNamespace("pre-auth-key-namespace") | ||||
| 	user, err := s.createUser("pre-auth-key-user") | ||||
| 
 | ||||
| 	keys := make([]*v1.PreAuthKey, count) | ||||
| 	assert.Nil(s.T(), err) | ||||
| @ -262,8 +262,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"preauthkeys", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"create", | ||||
| 				"--reusable", | ||||
| 				"--expiration", | ||||
| @ -292,8 +292,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -357,8 +357,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"preauthkeys", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"expire", | ||||
| 				listedPreAuthKeys[i].Key, | ||||
| 			}, | ||||
| @ -373,8 +373,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -410,7 +410,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() { | ||||
| 	namespace, err := s.createNamespace("pre-auth-key-without-exp-namespace") | ||||
| 	user, err := s.createUser("pre-auth-key-without-exp-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	preAuthResult, _, err := ExecuteCommand( | ||||
| @ -418,8 +418,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"create", | ||||
| 			"--reusable", | ||||
| 			"--output", | ||||
| @ -439,8 +439,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -463,7 +463,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| 	namespace, err := s.createNamespace("pre-auth-key-reus-ephm-namespace") | ||||
| 	user, err := s.createUser("pre-auth-key-reus-ephm-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	preAuthReusableResult, _, err := ExecuteCommand( | ||||
| @ -471,8 +471,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"create", | ||||
| 			"--reusable=true", | ||||
| 			"--output", | ||||
| @ -494,8 +494,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"create", | ||||
| 			"--ephemeral=true", | ||||
| 			"--output", | ||||
| @ -518,8 +518,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| 	// 	[]string{ | ||||
| 	// 		"headscale", | ||||
| 	// 		"preauthkeys", | ||||
| 	// 		"--namespace", | ||||
| 	// 		namespace.Name, | ||||
| 	// 		"--user", | ||||
| 	// 		user.Name, | ||||
| 	// 		"create", | ||||
| 	// 		"--ephemeral", | ||||
| 	// 		"--reusable", | ||||
| @ -536,8 +536,8 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"preauthkeys", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"list", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| @ -554,7 +554,7 @@ func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNodeTagCommand() { | ||||
| 	namespace, err := s.createNamespace("machine-namespace") | ||||
| 	user, err := s.createUser("machine-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	machineKeys := []string{ | ||||
| @ -573,8 +573,8 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() { | ||||
| 				"create-node", | ||||
| 				"--name", | ||||
| 				fmt.Sprintf("machine-%d", index+1), | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| 				"--output", | ||||
| @ -589,8 +589,8 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"nodes", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"register", | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| @ -683,10 +683,10 @@ func (s *IntegrationCLITestSuite) TestNodeTagCommand() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	namespace, err := s.createNamespace("machine-namespace") | ||||
| 	user, err := s.createUser("machine-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	secondNamespace, err := s.createNamespace("other-namespace") | ||||
| 	secondUser, err := s.createUser("other-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// Randomly generated machine keys | ||||
| @ -709,8 +709,8 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 				"create-node", | ||||
| 				"--name", | ||||
| 				fmt.Sprintf("machine-%d", index+1), | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| 				"--output", | ||||
| @ -725,8 +725,8 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"nodes", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"register", | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| @ -778,14 +778,14 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	assert.Equal(s.T(), "machine-4", listAll[3].Name) | ||||
| 	assert.Equal(s.T(), "machine-5", listAll[4].Name) | ||||
| 
 | ||||
| 	otherNamespaceMachineKeys := []string{ | ||||
| 	otherUserMachineKeys := []string{ | ||||
| 		"nodekey:b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e", | ||||
| 		"nodekey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584", | ||||
| 	} | ||||
| 	otherNamespaceMachines := make([]*v1.Machine, len(otherNamespaceMachineKeys)) | ||||
| 	otherUserMachines := make([]*v1.Machine, len(otherUserMachineKeys)) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	for index, machineKey := range otherNamespaceMachineKeys { | ||||
| 	for index, machineKey := range otherUserMachineKeys { | ||||
| 		_, _, err := ExecuteCommand( | ||||
| 			&s.headscale, | ||||
| 			[]string{ | ||||
| @ -793,9 +793,9 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 				"debug", | ||||
| 				"create-node", | ||||
| 				"--name", | ||||
| 				fmt.Sprintf("otherNamespace-machine-%d", index+1), | ||||
| 				"--namespace", | ||||
| 				secondNamespace.Name, | ||||
| 				fmt.Sprintf("otherUser-machine-%d", index+1), | ||||
| 				"--user", | ||||
| 				secondUser.Name, | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| 				"--output", | ||||
| @ -810,8 +810,8 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"nodes", | ||||
| 				"--namespace", | ||||
| 				secondNamespace.Name, | ||||
| 				"--user", | ||||
| 				secondUser.Name, | ||||
| 				"register", | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| @ -826,13 +826,13 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 		err = json.Unmarshal([]byte(machineResult), &machine) | ||||
| 		assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 		otherNamespaceMachines[index] = &machine | ||||
| 		otherUserMachines[index] = &machine | ||||
| 	} | ||||
| 
 | ||||
| 	assert.Len(s.T(), otherNamespaceMachines, len(otherNamespaceMachineKeys)) | ||||
| 	assert.Len(s.T(), otherUserMachines, len(otherUserMachineKeys)) | ||||
| 
 | ||||
| 	// Test list all nodes after added otherNamespace | ||||
| 	listAllWithotherNamespaceResult, _, err := ExecuteCommand( | ||||
| 	// Test list all nodes after added otherUser | ||||
| 	listAllWithotherUserResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| @ -845,31 +845,31 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var listAllWithotherNamespace []v1.Machine | ||||
| 	var listAllWithotherUser []v1.Machine | ||||
| 	err = json.Unmarshal( | ||||
| 		[]byte(listAllWithotherNamespaceResult), | ||||
| 		&listAllWithotherNamespace, | ||||
| 		[]byte(listAllWithotherUserResult), | ||||
| 		&listAllWithotherUser, | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// All nodes, machines + otherNamespace | ||||
| 	assert.Len(s.T(), listAllWithotherNamespace, 7) | ||||
| 	// All nodes, machines + otherUser | ||||
| 	assert.Len(s.T(), listAllWithotherUser, 7) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), uint64(6), listAllWithotherNamespace[5].Id) | ||||
| 	assert.Equal(s.T(), uint64(7), listAllWithotherNamespace[6].Id) | ||||
| 	assert.Equal(s.T(), uint64(6), listAllWithotherUser[5].Id) | ||||
| 	assert.Equal(s.T(), uint64(7), listAllWithotherUser[6].Id) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), "otherNamespace-machine-1", listAllWithotherNamespace[5].Name) | ||||
| 	assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].Name) | ||||
| 	assert.Equal(s.T(), "otherUser-machine-1", listAllWithotherUser[5].Name) | ||||
| 	assert.Equal(s.T(), "otherUser-machine-2", listAllWithotherUser[6].Name) | ||||
| 
 | ||||
| 	// Test list all nodes after added otherNamespace | ||||
| 	listOnlyotherNamespaceMachineNamespaceResult, _, err := ExecuteCommand( | ||||
| 	// Test list all nodes after added otherUser | ||||
| 	listOnlyotherUserMachineUserResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"nodes", | ||||
| 			"list", | ||||
| 			"--namespace", | ||||
| 			secondNamespace.Name, | ||||
| 			"--user", | ||||
| 			secondUser.Name, | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -877,27 +877,27 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var listOnlyotherNamespaceMachineNamespace []v1.Machine | ||||
| 	var listOnlyotherUserMachineUser []v1.Machine | ||||
| 	err = json.Unmarshal( | ||||
| 		[]byte(listOnlyotherNamespaceMachineNamespaceResult), | ||||
| 		&listOnlyotherNamespaceMachineNamespace, | ||||
| 		[]byte(listOnlyotherUserMachineUserResult), | ||||
| 		&listOnlyotherUserMachineUser, | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Len(s.T(), listOnlyotherNamespaceMachineNamespace, 2) | ||||
| 	assert.Len(s.T(), listOnlyotherUserMachineUser, 2) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), uint64(6), listOnlyotherNamespaceMachineNamespace[0].Id) | ||||
| 	assert.Equal(s.T(), uint64(7), listOnlyotherNamespaceMachineNamespace[1].Id) | ||||
| 	assert.Equal(s.T(), uint64(6), listOnlyotherUserMachineUser[0].Id) | ||||
| 	assert.Equal(s.T(), uint64(7), listOnlyotherUserMachineUser[1].Id) | ||||
| 
 | ||||
| 	assert.Equal( | ||||
| 		s.T(), | ||||
| 		"otherNamespace-machine-1", | ||||
| 		listOnlyotherNamespaceMachineNamespace[0].Name, | ||||
| 		"otherUser-machine-1", | ||||
| 		listOnlyotherUserMachineUser[0].Name, | ||||
| 	) | ||||
| 	assert.Equal( | ||||
| 		s.T(), | ||||
| 		"otherNamespace-machine-2", | ||||
| 		listOnlyotherNamespaceMachineNamespace[1].Name, | ||||
| 		"otherUser-machine-2", | ||||
| 		listOnlyotherUserMachineUser[1].Name, | ||||
| 	) | ||||
| 
 | ||||
| 	// Delete a machines | ||||
| @ -918,15 +918,15 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// Test: list main namespace after machine is deleted | ||||
| 	listOnlyMachineNamespaceAfterDeleteResult, _, err := ExecuteCommand( | ||||
| 	// Test: list main user after machine is deleted | ||||
| 	listOnlyMachineUserAfterDeleteResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"nodes", | ||||
| 			"list", | ||||
| 			"--namespace", | ||||
| 			namespace.Name, | ||||
| 			"--user", | ||||
| 			user.Name, | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -934,18 +934,18 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	var listOnlyMachineNamespaceAfterDelete []v1.Machine | ||||
| 	var listOnlyMachineUserAfterDelete []v1.Machine | ||||
| 	err = json.Unmarshal( | ||||
| 		[]byte(listOnlyMachineNamespaceAfterDeleteResult), | ||||
| 		&listOnlyMachineNamespaceAfterDelete, | ||||
| 		[]byte(listOnlyMachineUserAfterDeleteResult), | ||||
| 		&listOnlyMachineUserAfterDelete, | ||||
| 	) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Len(s.T(), listOnlyMachineNamespaceAfterDelete, 4) | ||||
| 	assert.Len(s.T(), listOnlyMachineUserAfterDelete, 4) | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNodeExpireCommand() { | ||||
| 	namespace, err := s.createNamespace("machine-expire-namespace") | ||||
| 	user, err := s.createUser("machine-expire-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// Randomly generated machine keys | ||||
| @ -968,8 +968,8 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() { | ||||
| 				"create-node", | ||||
| 				"--name", | ||||
| 				fmt.Sprintf("machine-%d", index+1), | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| 				"--output", | ||||
| @ -984,8 +984,8 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"nodes", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"register", | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| @ -1072,7 +1072,7 @@ func (s *IntegrationCLITestSuite) TestNodeExpireCommand() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNodeRenameCommand() { | ||||
| 	namespace, err := s.createNamespace("machine-rename-command") | ||||
| 	user, err := s.createUser("machine-rename-command") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// Randomly generated machine keys | ||||
| @ -1095,8 +1095,8 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() { | ||||
| 				"create-node", | ||||
| 				"--name", | ||||
| 				fmt.Sprintf("machine-%d", index+1), | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| 				"--output", | ||||
| @ -1111,8 +1111,8 @@ func (s *IntegrationCLITestSuite) TestNodeRenameCommand() { | ||||
| 			[]string{ | ||||
| 				"headscale", | ||||
| 				"nodes", | ||||
| 				"--namespace", | ||||
| 				namespace.Name, | ||||
| 				"--user", | ||||
| 				user.Name, | ||||
| 				"register", | ||||
| 				"--key", | ||||
| 				machineKey, | ||||
| @ -1389,9 +1389,9 @@ func (s *IntegrationCLITestSuite) TestApiKeyCommand() { | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	oldNamespace, err := s.createNamespace("old-namespace") | ||||
| 	oldUser, err := s.createUser("old-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 	newNamespace, err := s.createNamespace("new-namespace") | ||||
| 	newUser, err := s.createUser("new-user") | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	// Randomly generated machine key | ||||
| @ -1405,8 +1405,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 			"create-node", | ||||
| 			"--name", | ||||
| 			"nomad-machine", | ||||
| 			"--namespace", | ||||
| 			oldNamespace.Name, | ||||
| 			"--user", | ||||
| 			oldUser.Name, | ||||
| 			"--key", | ||||
| 			machineKey, | ||||
| 			"--output", | ||||
| @ -1421,8 +1421,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"nodes", | ||||
| 			"--namespace", | ||||
| 			oldNamespace.Name, | ||||
| 			"--user", | ||||
| 			oldUser.Name, | ||||
| 			"register", | ||||
| 			"--key", | ||||
| 			machineKey, | ||||
| @ -1439,7 +1439,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 
 | ||||
| 	assert.Equal(s.T(), uint64(1), machine.Id) | ||||
| 	assert.Equal(s.T(), "nomad-machine", machine.Name) | ||||
| 	assert.Equal(s.T(), machine.Namespace.Name, oldNamespace.Name) | ||||
| 	assert.Equal(s.T(), machine.User.Name, oldUser.Name) | ||||
| 
 | ||||
| 	machineId := fmt.Sprintf("%d", machine.Id) | ||||
| 
 | ||||
| @ -1451,8 +1451,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 			"move", | ||||
| 			"--identifier", | ||||
| 			machineId, | ||||
| 			"--namespace", | ||||
| 			newNamespace.Name, | ||||
| 			"--user", | ||||
| 			newUser.Name, | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -1463,7 +1463,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	err = json.Unmarshal([]byte(moveToNewNSResult), &machine) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), machine.Namespace, newNamespace) | ||||
| 	assert.Equal(s.T(), machine.User, newUser) | ||||
| 
 | ||||
| 	listAllNodesResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -1485,8 +1485,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	assert.Len(s.T(), allNodes, 1) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), allNodes[0].Id, machine.Id) | ||||
| 	assert.Equal(s.T(), allNodes[0].Namespace, machine.Namespace) | ||||
| 	assert.Equal(s.T(), allNodes[0].Namespace, newNamespace) | ||||
| 	assert.Equal(s.T(), allNodes[0].User, machine.User) | ||||
| 	assert.Equal(s.T(), allNodes[0].User, newUser) | ||||
| 
 | ||||
| 	moveToNonExistingNSResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -1496,8 +1496,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 			"move", | ||||
| 			"--identifier", | ||||
| 			machineId, | ||||
| 			"--namespace", | ||||
| 			"non-existing-namespace", | ||||
| 			"--user", | ||||
| 			"non-existing-user", | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -1508,9 +1508,9 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	assert.Contains( | ||||
| 		s.T(), | ||||
| 		string(moveToNonExistingNSResult), | ||||
| 		"Namespace not found", | ||||
| 		"User not found", | ||||
| 	) | ||||
| 	assert.Equal(s.T(), machine.Namespace, newNamespace) | ||||
| 	assert.Equal(s.T(), machine.User, newUser) | ||||
| 
 | ||||
| 	moveToOldNSResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -1520,8 +1520,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 			"move", | ||||
| 			"--identifier", | ||||
| 			machineId, | ||||
| 			"--namespace", | ||||
| 			oldNamespace.Name, | ||||
| 			"--user", | ||||
| 			oldUser.Name, | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -1532,7 +1532,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	err = json.Unmarshal([]byte(moveToOldNSResult), &machine) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), machine.Namespace, oldNamespace) | ||||
| 	assert.Equal(s.T(), machine.User, oldUser) | ||||
| 
 | ||||
| 	moveToSameNSResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -1542,8 +1542,8 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 			"move", | ||||
| 			"--identifier", | ||||
| 			machineId, | ||||
| 			"--namespace", | ||||
| 			oldNamespace.Name, | ||||
| 			"--user", | ||||
| 			oldUser.Name, | ||||
| 			"--output", | ||||
| 			"json", | ||||
| 		}, | ||||
| @ -1554,7 +1554,7 @@ func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { | ||||
| 	err = json.Unmarshal([]byte(moveToSameNSResult), &machine) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	assert.Equal(s.T(), machine.Namespace, oldNamespace) | ||||
| 	assert.Equal(s.T(), machine.User, oldUser) | ||||
| } | ||||
| 
 | ||||
| func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() { | ||||
|  | ||||
| @ -47,7 +47,7 @@ var ( | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| type TestNamespace struct { | ||||
| type TestUser struct { | ||||
| 	count      int | ||||
| 	tailscales map[string]dockertest.Resource | ||||
| } | ||||
| @ -296,7 +296,7 @@ func getMagicFQDN( | ||||
| 		hostnames[index] = fmt.Sprintf( | ||||
| 			"%s.%s.headscale.net", | ||||
| 			listAll[index].GetGivenName(), | ||||
| 			listAll[index].GetNamespace().GetName(), | ||||
| 			listAll[index].GetUser().GetName(), | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,7 @@ import ( | ||||
| 
 | ||||
| const ( | ||||
| 	headscaleDerpHostname = "headscale-derp" | ||||
| 	namespaceName         = "derpnamespace" | ||||
| 	userName         = "derpuser" | ||||
| 	totalContainers       = 3 | ||||
| ) | ||||
| 
 | ||||
| @ -199,22 +199,22 @@ func (s *IntegrationDERPTestSuite) SetupSuite() { | ||||
| 	} | ||||
| 	log.Println("headscale container is ready for embedded DERP tests") | ||||
| 
 | ||||
| 	log.Printf("Creating headscale namespace: %s\n", namespaceName) | ||||
| 	log.Printf("Creating headscale user: %s\n", userName) | ||||
| 	result, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{"headscale", "namespaces", "create", namespaceName}, | ||||
| 		[]string{"headscale", "users", "create", userName}, | ||||
| 		[]string{}, | ||||
| 	) | ||||
| 	log.Println("headscale create namespace result: ", result) | ||||
| 	log.Println("headscale create user result: ", result) | ||||
| 	assert.Nil(s.T(), err) | ||||
| 
 | ||||
| 	log.Printf("Creating pre auth key for %s\n", namespaceName) | ||||
| 	log.Printf("Creating pre auth key for %s\n", userName) | ||||
| 	preAuthResult, _, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| 		[]string{ | ||||
| 			"headscale", | ||||
| 			"--namespace", | ||||
| 			namespaceName, | ||||
| 			"--user", | ||||
| 			userName, | ||||
| 			"preauthkeys", | ||||
| 			"create", | ||||
| 			"--reusable", | ||||
|  | ||||
							
								
								
									
										72
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								machine.go
									
									
									
									
									
								
							| @ -27,8 +27,8 @@ const ( | ||||
| 	) | ||||
| 	ErrCouldNotConvertMachineInterface = Error("failed to convert machine interface") | ||||
| 	ErrHostnameTooLong                 = Error("Hostname too long") | ||||
| 	ErrDifferentRegisteredNamespace    = Error( | ||||
| 		"machine was previously registered with a different namespace", | ||||
| 	ErrDifferentRegisteredUser    = Error( | ||||
| 		"machine was previously registered with a different user", | ||||
| 	) | ||||
| 	MachineGivenNameHashLength = 8 | ||||
| 	MachineGivenNameTrimSize   = 2 | ||||
| @ -57,8 +57,8 @@ type Machine struct { | ||||
| 	// GivenName is the name used in all DNS related | ||||
| 	// parts of headscale. | ||||
| 	GivenName   string `gorm:"type:varchar(63);unique_index"` | ||||
| 	NamespaceID uint | ||||
| 	Namespace   Namespace `gorm:"foreignKey:NamespaceID"` | ||||
| 	UserID uint | ||||
| 	User   User `gorm:"foreignKey:UserID"` | ||||
| 
 | ||||
| 	RegisterMethod string | ||||
| 
 | ||||
| @ -192,7 +192,7 @@ func getFilteredByACLPeers( | ||||
| 		Msg("Finding peers filtered by ACLs") | ||||
| 
 | ||||
| 	peers := make(map[uint64]Machine) | ||||
| 	// Aclfilter peers here. We are itering through machines in all namespaces and search through the computed aclRules | ||||
| 	// Aclfilter peers here. We are itering through machines in all users and search through the computed aclRules | ||||
| 	// for match between rule SrcIPs and DstPorts. If the rule is a match we allow the machine to be viewable. | ||||
| 	machineIPs := machine.IPAddresses.ToStringSlice() | ||||
| 	for _, peer := range machines { | ||||
| @ -270,7 +270,7 @@ func (h *Headscale) ListPeers(machine *Machine) (Machines, error) { | ||||
| 		Msg("Finding direct peers") | ||||
| 
 | ||||
| 	machines := Machines{} | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("node_key <> ?", | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where("node_key <> ?", | ||||
| 		machine.NodeKey).Find(&machines).Error; err != nil { | ||||
| 		log.Error().Err(err).Msg("Error accessing db") | ||||
| 
 | ||||
| @ -292,7 +292,7 @@ func (h *Headscale) getPeers(machine *Machine) (Machines, error) { | ||||
| 	var err error | ||||
| 
 | ||||
| 	// If ACLs rules are defined, filter visible host list with the ACLs | ||||
| 	// else use the classic namespace scope | ||||
| 	// else use the classic user scope | ||||
| 	if h.aclPolicy != nil { | ||||
| 		var machines []Machine | ||||
| 		machines, err = h.ListMachines() | ||||
| @ -343,7 +343,7 @@ func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) { | ||||
| 
 | ||||
| func (h *Headscale) ListMachines() ([]Machine, error) { | ||||
| 	machines := []Machine{} | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Find(&machines).Error; err != nil { | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Find(&machines).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| @ -352,16 +352,16 @@ func (h *Headscale) ListMachines() ([]Machine, error) { | ||||
| 
 | ||||
| func (h *Headscale) ListMachinesByGivenName(givenName string) ([]Machine, error) { | ||||
| 	machines := []Machine{} | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Find(&machines).Where("given_name = ?", givenName).Error; err != nil { | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Find(&machines).Where("given_name = ?", givenName).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return machines, nil | ||||
| } | ||||
| 
 | ||||
| // GetMachine finds a Machine by name and namespace and returns the Machine struct. | ||||
| func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error) { | ||||
| 	machines, err := h.ListMachinesInNamespace(namespace) | ||||
| // GetMachine finds a Machine by name and user and returns the Machine struct. | ||||
| func (h *Headscale) GetMachine(user string, name string) (*Machine, error) { | ||||
| 	machines, err := h.ListMachinesByUser(user) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -375,9 +375,9 @@ func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error) | ||||
| 	return nil, ErrMachineNotFound | ||||
| } | ||||
| 
 | ||||
| // GetMachineByGivenName finds a Machine by given name and namespace and returns the Machine struct. | ||||
| func (h *Headscale) GetMachineByGivenName(namespace string, givenName string) (*Machine, error) { | ||||
| 	machines, err := h.ListMachinesInNamespace(namespace) | ||||
| // GetMachineByGivenName finds a Machine by given name and user and returns the Machine struct. | ||||
| func (h *Headscale) GetMachineByGivenName(user string, givenName string) (*Machine, error) { | ||||
| 	machines, err := h.ListMachinesByUser(user) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -394,7 +394,7 @@ func (h *Headscale) GetMachineByGivenName(namespace string, givenName string) (* | ||||
| // GetMachineByID finds a Machine by ID and returns the Machine struct. | ||||
| func (h *Headscale) GetMachineByID(id uint64) (*Machine, error) { | ||||
| 	m := Machine{} | ||||
| 	if result := h.db.Preload("AuthKey").Preload("Namespace").Find(&Machine{ID: id}).First(&m); result.Error != nil { | ||||
| 	if result := h.db.Preload("AuthKey").Preload("User").Find(&Machine{ID: id}).First(&m); result.Error != nil { | ||||
| 		return nil, result.Error | ||||
| 	} | ||||
| 
 | ||||
| @ -406,7 +406,7 @@ func (h *Headscale) GetMachineByMachineKey( | ||||
| 	machineKey key.MachinePublic, | ||||
| ) (*Machine, error) { | ||||
| 	m := Machine{} | ||||
| 	if result := h.db.Preload("AuthKey").Preload("Namespace").First(&m, "machine_key = ?", MachinePublicKeyStripPrefix(machineKey)); result.Error != nil { | ||||
| 	if result := h.db.Preload("AuthKey").Preload("User").First(&m, "machine_key = ?", MachinePublicKeyStripPrefix(machineKey)); result.Error != nil { | ||||
| 		return nil, result.Error | ||||
| 	} | ||||
| 
 | ||||
| @ -418,7 +418,7 @@ func (h *Headscale) GetMachineByNodeKey( | ||||
| 	nodeKey key.NodePublic, | ||||
| ) (*Machine, error) { | ||||
| 	machine := Machine{} | ||||
| 	if result := h.db.Preload("AuthKey").Preload("Namespace").First(&machine, "node_key = ?", | ||||
| 	if result := h.db.Preload("AuthKey").Preload("User").First(&machine, "node_key = ?", | ||||
| 		NodePublicKeyStripPrefix(nodeKey)); result.Error != nil { | ||||
| 		return nil, result.Error | ||||
| 	} | ||||
| @ -431,7 +431,7 @@ func (h *Headscale) GetMachineByAnyKey( | ||||
| 	machineKey key.MachinePublic, nodeKey key.NodePublic, oldNodeKey key.NodePublic, | ||||
| ) (*Machine, error) { | ||||
| 	machine := Machine{} | ||||
| 	if result := h.db.Preload("AuthKey").Preload("Namespace").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?", | ||||
| 	if result := h.db.Preload("AuthKey").Preload("User").First(&machine, "machine_key = ? OR node_key = ? OR node_key = ?", | ||||
| 		MachinePublicKeyStripPrefix(machineKey), | ||||
| 		NodePublicKeyStripPrefix(nodeKey), | ||||
| 		NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil { | ||||
| @ -570,9 +570,9 @@ func (h *Headscale) isOutdated(machine *Machine) bool { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the last update from all headscale namespaces to compare with our nodes | ||||
| 	// Get the last update from all headscale users to compare with our nodes | ||||
| 	// last update. | ||||
| 	// TODO(kradalby): Only request updates from namespaces where we can talk to nodes | ||||
| 	// TODO(kradalby): Only request updates from users where we can talk to nodes | ||||
| 	// This would mostly be for a bit of performance, and can be calculated based on | ||||
| 	// ACLs. | ||||
| 	lastChange := h.getLastStateChange() | ||||
| @ -720,7 +720,7 @@ func (h *Headscale) toNode( | ||||
| 		hostname = fmt.Sprintf( | ||||
| 			"%s.%s.%s", | ||||
| 			machine.GivenName, | ||||
| 			machine.Namespace.Name, | ||||
| 			machine.User.Name, | ||||
| 			baseDomain, | ||||
| 		) | ||||
| 		if len(hostname) > maxHostnameLength { | ||||
| @ -744,7 +744,7 @@ func (h *Headscale) toNode( | ||||
| 			strconv.FormatUint(machine.ID, Base10), | ||||
| 		), // in headscale, unlike tailcontrol server, IDs are permanent | ||||
| 		Name:          hostname, | ||||
| 		User:          tailcfg.UserID(machine.NamespaceID), | ||||
| 		User:          tailcfg.UserID(machine.UserID), | ||||
| 		Key:           nodeKey, | ||||
| 		KeyExpiry:     keyExpiry, | ||||
| 		Machine:       machineKey, | ||||
| @ -782,7 +782,7 @@ func (machine *Machine) toProto() *v1.Machine { | ||||
| 		IpAddresses: machine.IPAddresses.ToStringSlice(), | ||||
| 		Name:        machine.Hostname, | ||||
| 		GivenName:   machine.GivenName, | ||||
| 		Namespace:   machine.Namespace.toProto(), | ||||
| 		User:   machine.User.toProto(), | ||||
| 		ForcedTags:  machine.ForcedTags, | ||||
| 		Online:      machine.isOnline(), | ||||
| 
 | ||||
| @ -837,7 +837,7 @@ func getTags( | ||||
| 		} | ||||
| 		var found bool | ||||
| 		for _, owner := range owners { | ||||
| 			if machine.Namespace.Name == owner { | ||||
| 			if machine.User.Name == owner { | ||||
| 				found = true | ||||
| 			} | ||||
| 		} | ||||
| @ -859,7 +859,7 @@ func getTags( | ||||
| 
 | ||||
| func (h *Headscale) RegisterMachineFromAuthCallback( | ||||
| 	nodeKeyStr string, | ||||
| 	namespaceName string, | ||||
| 	userName string, | ||||
| 	machineExpiry *time.Time, | ||||
| 	registrationMethod string, | ||||
| ) (*Machine, error) { | ||||
| @ -871,28 +871,28 @@ func (h *Headscale) RegisterMachineFromAuthCallback( | ||||
| 
 | ||||
| 	log.Debug(). | ||||
| 		Str("nodeKey", nodeKey.ShortString()). | ||||
| 		Str("namespaceName", namespaceName). | ||||
| 		Str("userName", userName). | ||||
| 		Str("registrationMethod", registrationMethod). | ||||
| 		Str("expiresAt", fmt.Sprintf("%v", machineExpiry)). | ||||
| 		Msg("Registering machine from API/CLI or auth callback") | ||||
| 
 | ||||
| 	if machineInterface, ok := h.registrationCache.Get(NodePublicKeyStripPrefix(nodeKey)); ok { | ||||
| 		if registrationMachine, ok := machineInterface.(Machine); ok { | ||||
| 			namespace, err := h.GetNamespace(namespaceName) | ||||
| 			user, err := h.GetUser(userName) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf( | ||||
| 					"failed to find namespace in register machine from auth callback, %w", | ||||
| 					"failed to find user in register machine from auth callback, %w", | ||||
| 					err, | ||||
| 				) | ||||
| 			} | ||||
| 
 | ||||
| 			// Registration of expired machine with different namespace | ||||
| 			// Registration of expired machine with different user | ||||
| 			if registrationMachine.ID != 0 && | ||||
| 				registrationMachine.NamespaceID != namespace.ID { | ||||
| 				return nil, ErrDifferentRegisteredNamespace | ||||
| 				registrationMachine.UserID != user.ID { | ||||
| 				return nil, ErrDifferentRegisteredUser | ||||
| 			} | ||||
| 
 | ||||
| 			registrationMachine.NamespaceID = namespace.ID | ||||
| 			registrationMachine.UserID = user.ID | ||||
| 			registrationMachine.RegisterMethod = registrationMethod | ||||
| 
 | ||||
| 			if machineExpiry != nil { | ||||
| @ -923,7 +923,7 @@ func (h *Headscale) RegisterMachine(machine Machine, | ||||
| 		Str("machine", machine.Hostname). | ||||
| 		Str("machine_key", machine.MachineKey). | ||||
| 		Str("node_key", machine.NodeKey). | ||||
| 		Str("namespace", machine.Namespace.Name). | ||||
| 		Str("user", machine.User.Name). | ||||
| 		Msg("Registering machine") | ||||
| 
 | ||||
| 	// If the machine exists and we had already IPs for it, we just save it | ||||
| @ -939,7 +939,7 @@ func (h *Headscale) RegisterMachine(machine Machine, | ||||
| 			Str("machine", machine.Hostname). | ||||
| 			Str("machine_key", machine.MachineKey). | ||||
| 			Str("node_key", machine.NodeKey). | ||||
| 			Str("namespace", machine.Namespace.Name). | ||||
| 			Str("user", machine.User.Name). | ||||
| 			Msg("Machine authorized again") | ||||
| 
 | ||||
| 		return &machine, nil | ||||
| @ -1137,7 +1137,7 @@ func (h *Headscale) EnableAutoApprovedRoutes(machine *Machine) error { | ||||
| 		} | ||||
| 
 | ||||
| 		for _, approvedAlias := range routeApprovers { | ||||
| 			if approvedAlias == machine.Namespace.Name { | ||||
| 			if approvedAlias == machine.User.Name { | ||||
| 				approvedRoutes = append(approvedRoutes, advertisedRoute) | ||||
| 			} else { | ||||
| 				approvedIps, err := expandAlias([]Machine{*machine}, *h.aclPolicy, approvedAlias, h.cfg.OIDC.StripEmaildomain) | ||||
|  | ||||
							
								
								
									
										174
									
								
								machine_test.go
									
									
									
									
									
								
							
							
						
						
									
										174
									
								
								machine_test.go
									
									
									
									
									
								
							| @ -15,10 +15,10 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestGetMachine(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| @ -30,7 +30,7 @@ func (s *Suite) TestGetMachine(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -41,10 +41,10 @@ func (s *Suite) TestGetMachine(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMachineByID(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachineByID(0) | ||||
| @ -56,7 +56,7 @@ func (s *Suite) TestGetMachineByID(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -67,10 +67,10 @@ func (s *Suite) TestGetMachineByID(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMachineByNodeKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachineByID(0) | ||||
| @ -85,7 +85,7 @@ func (s *Suite) TestGetMachineByNodeKey(c *check.C) { | ||||
| 		NodeKey:        NodePublicKeyStripPrefix(nodeKey.Public()), | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -96,10 +96,10 @@ func (s *Suite) TestGetMachineByNodeKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachineByID(0) | ||||
| @ -116,7 +116,7 @@ func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) { | ||||
| 		NodeKey:        NodePublicKeyStripPrefix(nodeKey.Public()), | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -127,7 +127,7 @@ func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestDeleteMachine(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	machine := Machine{ | ||||
| 		ID:             0, | ||||
| @ -135,7 +135,7 @@ func (s *Suite) TestDeleteMachine(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(1), | ||||
| 	} | ||||
| @ -144,12 +144,12 @@ func (s *Suite) TestDeleteMachine(c *check.C) { | ||||
| 	err = app.DeleteMachine(&machine) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespace.Name, "testmachine") | ||||
| 	_, err = app.GetMachine(user.Name, "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestHardDeleteMachine(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	machine := Machine{ | ||||
| 		ID:             0, | ||||
| @ -157,7 +157,7 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine3", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(1), | ||||
| 	} | ||||
| @ -166,15 +166,15 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) { | ||||
| 	err = app.HardDeleteMachine(&machine) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespace.Name, "testmachine3") | ||||
| 	_, err = app.GetMachine(user.Name, "testmachine3") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestListPeers(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachineByID(0) | ||||
| @ -187,7 +187,7 @@ func (s *Suite) TestListPeers(c *check.C) { | ||||
| 			NodeKey:        "bar" + strconv.Itoa(index), | ||||
| 			DiscoKey:       "faa" + strconv.Itoa(index), | ||||
| 			Hostname:       "testmachine" + strconv.Itoa(index), | ||||
| 			NamespaceID:    namespace.ID, | ||||
| 			UserID:    user.ID, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(pak.ID), | ||||
| 		} | ||||
| @ -208,18 +208,18 @@ func (s *Suite) TestListPeers(c *check.C) { | ||||
| 
 | ||||
| func (s *Suite) TestGetACLFilteredPeers(c *check.C) { | ||||
| 	type base struct { | ||||
| 		namespace *Namespace | ||||
| 		user *User | ||||
| 		key       *PreAuthKey | ||||
| 	} | ||||
| 
 | ||||
| 	stor := make([]base, 0) | ||||
| 
 | ||||
| 	for _, name := range []string{"test", "admin"} { | ||||
| 		namespace, err := app.CreateNamespace(name) | ||||
| 		user, err := app.CreateUser(name) | ||||
| 		c.Assert(err, check.IsNil) | ||||
| 		pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 		pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 		c.Assert(err, check.IsNil) | ||||
| 		stor = append(stor, base{namespace, pak}) | ||||
| 		stor = append(stor, base{user, pak}) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err := app.GetMachineByID(0) | ||||
| @ -235,7 +235,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { | ||||
| 				netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))), | ||||
| 			}, | ||||
| 			Hostname:       "testmachine" + strconv.Itoa(index), | ||||
| 			NamespaceID:    stor[index%2].namespace.ID, | ||||
| 			UserID:    stor[index%2].user.ID, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(stor[index%2].key.ID), | ||||
| 		} | ||||
| @ -267,11 +267,11 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	adminMachine, err := app.GetMachineByID(1) | ||||
| 	c.Logf("Machine(%v), namespace: %v", adminMachine.Hostname, adminMachine.Namespace) | ||||
| 	c.Logf("Machine(%v), user: %v", adminMachine.Hostname, adminMachine.User) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	testMachine, err := app.GetMachineByID(2) | ||||
| 	c.Logf("Machine(%v), namespace: %v", testMachine.Hostname, testMachine.Namespace) | ||||
| 	c.Logf("Machine(%v), user: %v", testMachine.Hostname, testMachine.User) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machines, err := app.ListMachines() | ||||
| @ -294,10 +294,10 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestExpireMachine(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| @ -309,7 +309,7 @@ func (s *Suite) TestExpireMachine(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		Expiry:         &time.Time{}, | ||||
| @ -350,13 +350,13 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGenerateGivenName(c *check.C) { | ||||
| 	namespace1, err := app.CreateNamespace("namespace-1") | ||||
| 	user1, err := app.CreateUser("user-1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace1.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user1.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("namespace-1", "testmachine") | ||||
| 	_, err = app.GetMachine("user-1", "testmachine") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	machine := &Machine{ | ||||
| @ -366,38 +366,38 @@ func (s *Suite) TestGenerateGivenName(c *check.C) { | ||||
| 		DiscoKey:       "disco-key-1", | ||||
| 		Hostname:       "hostname-1", | ||||
| 		GivenName:      "hostname-1", | ||||
| 		NamespaceID:    namespace1.ID, | ||||
| 		UserID:    user1.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 	app.db.Save(machine) | ||||
| 
 | ||||
| 	givenName, err := app.GenerateGivenName("machine-key-2", "hostname-2") | ||||
| 	comment := check.Commentf("Same namespace, unique machines, unique hostnames, no conflict") | ||||
| 	comment := check.Commentf("Same user, unique machines, unique hostnames, no conflict") | ||||
| 	c.Assert(err, check.IsNil, comment) | ||||
| 	c.Assert(givenName, check.Equals, "hostname-2", comment) | ||||
| 
 | ||||
| 	givenName, err = app.GenerateGivenName("machine-key-1", "hostname-1") | ||||
| 	comment = check.Commentf("Same namespace, same machine, same hostname, no conflict") | ||||
| 	comment = check.Commentf("Same user, same machine, same hostname, no conflict") | ||||
| 	c.Assert(err, check.IsNil, comment) | ||||
| 	c.Assert(givenName, check.Equals, "hostname-1", comment) | ||||
| 
 | ||||
| 	givenName, err = app.GenerateGivenName("machine-key-2", "hostname-1") | ||||
| 	comment = check.Commentf("Same namespace, unique machines, same hostname, conflict") | ||||
| 	comment = check.Commentf("Same user, unique machines, same hostname, conflict") | ||||
| 	c.Assert(err, check.IsNil, comment) | ||||
| 	c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment) | ||||
| 
 | ||||
| 	givenName, err = app.GenerateGivenName("machine-key-2", "hostname-1") | ||||
| 	comment = check.Commentf("Unique namespaces, unique machines, same hostname, conflict") | ||||
| 	comment = check.Commentf("Unique users, unique machines, same hostname, conflict") | ||||
| 	c.Assert(err, check.IsNil, comment) | ||||
| 	c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", MachineGivenNameHashLength), comment) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestSetTags(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| @ -409,7 +409,7 @@ func (s *Suite) TestSetTags(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -457,7 +457,7 @@ func Test_getTags(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: Machine{ | ||||
| 					Namespace: Namespace{ | ||||
| 					User: User{ | ||||
| 						Name: "joe", | ||||
| 					}, | ||||
| 					HostInfo: HostInfo{ | ||||
| @ -478,7 +478,7 @@ func Test_getTags(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: Machine{ | ||||
| 					Namespace: Namespace{ | ||||
| 					User: User{ | ||||
| 						Name: "joe", | ||||
| 					}, | ||||
| 					HostInfo: HostInfo{ | ||||
| @ -499,7 +499,7 @@ func Test_getTags(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: Machine{ | ||||
| 					Namespace: Namespace{ | ||||
| 					User: User{ | ||||
| 						Name: "joe", | ||||
| 					}, | ||||
| 					HostInfo: HostInfo{ | ||||
| @ -524,7 +524,7 @@ func Test_getTags(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: Machine{ | ||||
| 					Namespace: Namespace{ | ||||
| 					User: User{ | ||||
| 						Name: "joe", | ||||
| 					}, | ||||
| 					HostInfo: HostInfo{ | ||||
| @ -541,7 +541,7 @@ func Test_getTags(t *testing.T) { | ||||
| 			args: args{ | ||||
| 				aclPolicy: nil, | ||||
| 				machine: Machine{ | ||||
| 					Namespace: Namespace{ | ||||
| 					User: User{ | ||||
| 						Name: "joe", | ||||
| 					}, | ||||
| 					HostInfo: HostInfo{ | ||||
| @ -607,21 +607,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -635,19 +635,19 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				machine: &Machine{ // current machine | ||||
| 					ID:          1, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 					Namespace:   Namespace{Name: "joe"}, | ||||
| 					User:   User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 					User:   User{Name: "marc"}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:          3, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")}, | ||||
| 					Namespace:   Namespace{Name: "mickael"}, | ||||
| 					User:   User{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -660,21 +660,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -688,14 +688,14 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				machine: &Machine{ // current machine | ||||
| 					ID:          1, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")}, | ||||
| 					Namespace:   Namespace{Name: "joe"}, | ||||
| 					User:   User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 					User:   User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -708,21 +708,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -736,14 +736,14 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				machine: &Machine{ // current machine | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 					User:   User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID:          3, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")}, | ||||
| 					Namespace:   Namespace{Name: "mickael"}, | ||||
| 					User:   User{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -756,21 +756,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -786,7 +786,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| @ -795,7 +795,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.2"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "marc"}, | ||||
| 					User: User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -808,21 +808,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -838,7 +838,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.2"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "marc"}, | ||||
| 					User: User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| @ -847,14 +847,14 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID: 3, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.3"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "mickael"}, | ||||
| 					User: User{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -867,21 +867,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -895,7 +895,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				machine: &Machine{ // current machine | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 					User:   User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| @ -904,12 +904,12 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netip.MustParseAddr("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 					User: User{Name: "joe"}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:          3, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")}, | ||||
| 					Namespace:   Namespace{Name: "mickael"}, | ||||
| 					User:   User{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| @ -922,21 +922,21 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 						User: User{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 						User: User{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netip.MustParseAddr("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 						User: User{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered | ||||
| @ -944,7 +944,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				machine: &Machine{ // current machine | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 					User:   User{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{}, | ||||
| @ -1125,10 +1125,10 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) { | ||||
| 	err := app.LoadACLPolicy("./tests/acls/acl_policy_autoapprovers.hujson") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	nodeKey := key.NewNode() | ||||
| @ -1144,7 +1144,7 @@ func (s *Suite) TestAutoApproveRoutes(c *check.C) { | ||||
| 		NodeKey:        NodePublicKeyStripPrefix(nodeKey.Public()), | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo: HostInfo{ | ||||
|  | ||||
							
								
								
									
										12
									
								
								metrics.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								metrics.go
									
									
									
									
									
								
							| @ -8,34 +8,34 @@ import ( | ||||
| const prometheusNamespace = "headscale" | ||||
| 
 | ||||
| var ( | ||||
| 	// This is a high cardinality metric (namespace x machines), we might want to make this | ||||
| 	// This is a high cardinality metric (user x machines), we might want to make this | ||||
| 	// configurable/opt-in in the future. | ||||
| 	lastStateUpdate = promauto.NewGaugeVec(prometheus.GaugeOpts{ | ||||
| 		Namespace: prometheusNamespace, | ||||
| 		Name:      "last_update_seconds", | ||||
| 		Help:      "Time stamp in unix time when a machine or headscale was updated", | ||||
| 	}, []string{"namespace", "machine"}) | ||||
| 	}, []string{"user", "machine"}) | ||||
| 
 | ||||
| 	machineRegistrations = promauto.NewCounterVec(prometheus.CounterOpts{ | ||||
| 		Namespace: prometheusNamespace, | ||||
| 		Name:      "machine_registrations_total", | ||||
| 		Help:      "The total amount of registered machine attempts", | ||||
| 	}, []string{"action", "auth", "status", "namespace"}) | ||||
| 	}, []string{"action", "auth", "status", "user"}) | ||||
| 
 | ||||
| 	updateRequestsFromNode = promauto.NewCounterVec(prometheus.CounterOpts{ | ||||
| 		Namespace: prometheusNamespace, | ||||
| 		Name:      "update_request_from_node_total", | ||||
| 		Help:      "The number of updates requested by a node/update function", | ||||
| 	}, []string{"namespace", "machine", "state"}) | ||||
| 	}, []string{"user", "machine", "state"}) | ||||
| 	updateRequestsSentToNode = promauto.NewCounterVec(prometheus.CounterOpts{ | ||||
| 		Namespace: prometheusNamespace, | ||||
| 		Name:      "update_request_sent_to_node_total", | ||||
| 		Help:      "The number of calls/messages issued on a specific nodes update channel", | ||||
| 	}, []string{"namespace", "machine", "status"}) | ||||
| 	}, []string{"user", "machine", "status"}) | ||||
| 	// TODO(kradalby): This is very debugging, we might want to remove it. | ||||
| 	updateRequestsReceivedOnChannel = promauto.NewCounterVec(prometheus.CounterOpts{ | ||||
| 		Namespace: prometheusNamespace, | ||||
| 		Name:      "update_request_received_on_channel_total", | ||||
| 		Help:      "The number of update requests received on an update channel", | ||||
| 	}, []string{"namespace", "machine"}) | ||||
| 	}, []string{"user", "machine"}) | ||||
| ) | ||||
|  | ||||
							
								
								
									
										148
									
								
								namespaces.go
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								namespaces.go
									
									
									
									
									
								
							| @ -16,10 +16,10 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	ErrNamespaceExists          = Error("Namespace already exists") | ||||
| 	ErrNamespaceNotFound        = Error("Namespace not found") | ||||
| 	ErrNamespaceNotEmptyOfNodes = Error("Namespace not empty: node(s) found") | ||||
| 	ErrInvalidNamespaceName     = Error("Invalid namespace name") | ||||
| 	ErrUserExists        = Error("User already exists") | ||||
| 	ErrUserNotFound      = Error("User not found") | ||||
| 	ErrUserStillHasNodes = Error("User not empty: node(s) found") | ||||
| 	ErrInvalidUserName   = Error("Invalid user name") | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -27,55 +27,55 @@ const ( | ||||
| 	labelHostnameLength = 63 | ||||
| ) | ||||
| 
 | ||||
| var invalidCharsInNamespaceRegex = regexp.MustCompile("[^a-z0-9-.]+") | ||||
| var invalidCharsInUserRegex = regexp.MustCompile("[^a-z0-9-.]+") | ||||
| 
 | ||||
| // Namespace is the way Headscale implements the concept of users in Tailscale | ||||
| // User is the way Headscale implements the concept of users in Tailscale | ||||
| // | ||||
| // At the end of the day, users in Tailscale are some kind of 'bubbles' or namespaces | ||||
| // At the end of the day, users in Tailscale are some kind of 'bubbles' or users | ||||
| // that contain our machines. | ||||
| type Namespace struct { | ||||
| type User struct { | ||||
| 	gorm.Model | ||||
| 	Name string `gorm:"unique"` | ||||
| } | ||||
| 
 | ||||
| // CreateNamespace creates a new Namespace. Returns error if could not be created | ||||
| // or another namespace already exists. | ||||
| func (h *Headscale) CreateNamespace(name string) (*Namespace, error) { | ||||
| // CreateUser creates a new User. Returns error if could not be created | ||||
| // or another user already exists. | ||||
| func (h *Headscale) CreateUser(name string) (*User, error) { | ||||
| 	err := CheckForFQDNRules(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	namespace := Namespace{} | ||||
| 	if err := h.db.Where("name = ?", name).First(&namespace).Error; err == nil { | ||||
| 		return nil, ErrNamespaceExists | ||||
| 	user := User{} | ||||
| 	if err := h.db.Where("name = ?", name).First(&user).Error; err == nil { | ||||
| 		return nil, ErrUserExists | ||||
| 	} | ||||
| 	namespace.Name = name | ||||
| 	if err := h.db.Create(&namespace).Error; err != nil { | ||||
| 	user.Name = name | ||||
| 	if err := h.db.Create(&user).Error; err != nil { | ||||
| 		log.Error(). | ||||
| 			Str("func", "CreateNamespace"). | ||||
| 			Str("func", "CreateUser"). | ||||
| 			Err(err). | ||||
| 			Msg("Could not create row") | ||||
| 
 | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return &namespace, nil | ||||
| 	return &user, nil | ||||
| } | ||||
| 
 | ||||
| // DestroyNamespace destroys a Namespace. Returns error if the Namespace does | ||||
| // DestroyUser destroys a User. Returns error if the User does | ||||
| // not exist or if there are machines associated with it. | ||||
| func (h *Headscale) DestroyNamespace(name string) error { | ||||
| 	namespace, err := h.GetNamespace(name) | ||||
| func (h *Headscale) DestroyUser(name string) error { | ||||
| 	user, err := h.GetUser(name) | ||||
| 	if err != nil { | ||||
| 		return ErrNamespaceNotFound | ||||
| 		return ErrUserNotFound | ||||
| 	} | ||||
| 
 | ||||
| 	machines, err := h.ListMachinesInNamespace(name) | ||||
| 	machines, err := h.ListMachinesByUser(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(machines) > 0 { | ||||
| 		return ErrNamespaceNotEmptyOfNodes | ||||
| 		return ErrUserStillHasNodes | ||||
| 	} | ||||
| 
 | ||||
| 	keys, err := h.ListPreAuthKeys(name) | ||||
| @ -89,18 +89,18 @@ func (h *Headscale) DestroyNamespace(name string) error { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if result := h.db.Unscoped().Delete(&namespace); result.Error != nil { | ||||
| 	if result := h.db.Unscoped().Delete(&user); result.Error != nil { | ||||
| 		return result.Error | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RenameNamespace renames a Namespace. Returns error if the Namespace does | ||||
| // not exist or if another Namespace exists with the new name. | ||||
| func (h *Headscale) RenameNamespace(oldName, newName string) error { | ||||
| // RenameUser renames a User. Returns error if the User does | ||||
| // not exist or if another User exists with the new name. | ||||
| func (h *Headscale) RenameUser(oldName, newName string) error { | ||||
| 	var err error | ||||
| 	oldNamespace, err := h.GetNamespace(oldName) | ||||
| 	oldUser, err := h.GetUser(oldName) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -108,76 +108,76 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error { | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	_, err = h.GetNamespace(newName) | ||||
| 	_, err = h.GetUser(newName) | ||||
| 	if err == nil { | ||||
| 		return ErrNamespaceExists | ||||
| 		return ErrUserExists | ||||
| 	} | ||||
| 	if !errors.Is(err, ErrNamespaceNotFound) { | ||||
| 	if !errors.Is(err, ErrUserNotFound) { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	oldNamespace.Name = newName | ||||
| 	oldUser.Name = newName | ||||
| 
 | ||||
| 	if result := h.db.Save(&oldNamespace); result.Error != nil { | ||||
| 	if result := h.db.Save(&oldUser); result.Error != nil { | ||||
| 		return result.Error | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetNamespace fetches a namespace by name. | ||||
| func (h *Headscale) GetNamespace(name string) (*Namespace, error) { | ||||
| 	namespace := Namespace{} | ||||
| 	if result := h.db.First(&namespace, "name = ?", name); errors.Is( | ||||
| // GetUser fetches a user by name. | ||||
| func (h *Headscale) GetUser(name string) (*User, error) { | ||||
| 	user := User{} | ||||
| 	if result := h.db.First(&user, "name = ?", name); errors.Is( | ||||
| 		result.Error, | ||||
| 		gorm.ErrRecordNotFound, | ||||
| 	) { | ||||
| 		return nil, ErrNamespaceNotFound | ||||
| 		return nil, ErrUserNotFound | ||||
| 	} | ||||
| 
 | ||||
| 	return &namespace, nil | ||||
| 	return &user, nil | ||||
| } | ||||
| 
 | ||||
| // ListNamespaces gets all the existing namespaces. | ||||
| func (h *Headscale) ListNamespaces() ([]Namespace, error) { | ||||
| 	namespaces := []Namespace{} | ||||
| 	if err := h.db.Find(&namespaces).Error; err != nil { | ||||
| // ListUsers gets all the existing users. | ||||
| func (h *Headscale) ListUsers() ([]User, error) { | ||||
| 	users := []User{} | ||||
| 	if err := h.db.Find(&users).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return namespaces, nil | ||||
| 	return users, nil | ||||
| } | ||||
| 
 | ||||
| // ListMachinesInNamespace gets all the nodes in a given namespace. | ||||
| func (h *Headscale) ListMachinesInNamespace(name string) ([]Machine, error) { | ||||
| // ListMachinesByUser gets all the nodes in a given user. | ||||
| func (h *Headscale) ListMachinesByUser(name string) ([]Machine, error) { | ||||
| 	err := CheckForFQDNRules(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	namespace, err := h.GetNamespace(name) | ||||
| 	user, err := h.GetUser(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	machines := []Machine{} | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where(&Machine{NamespaceID: namespace.ID}).Find(&machines).Error; err != nil { | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.User").Preload("User").Where(&Machine{UserID: user.ID}).Find(&machines).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return machines, nil | ||||
| } | ||||
| 
 | ||||
| // SetMachineNamespace assigns a Machine to a namespace. | ||||
| func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string) error { | ||||
| 	err := CheckForFQDNRules(namespaceName) | ||||
| // SetMachineUser assigns a Machine to a user. | ||||
| func (h *Headscale) SetMachineUser(machine *Machine, username string) error { | ||||
| 	err := CheckForFQDNRules(username) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| 	user, err := h.GetUser(username) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	machine.Namespace = *namespace | ||||
| 	machine.User = *user | ||||
| 	if result := h.db.Save(&machine); result.Error != nil { | ||||
| 		return result.Error | ||||
| 	} | ||||
| @ -185,7 +185,7 @@ func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (n *Namespace) toUser() *tailcfg.User { | ||||
| func (n *User) toTailscaleUser() *tailcfg.User { | ||||
| 	user := tailcfg.User{ | ||||
| 		ID:            tailcfg.UserID(n.ID), | ||||
| 		LoginName:     n.Name, | ||||
| @ -199,7 +199,7 @@ func (n *Namespace) toUser() *tailcfg.User { | ||||
| 	return &user | ||||
| } | ||||
| 
 | ||||
| func (n *Namespace) toLogin() *tailcfg.Login { | ||||
| func (n *User) toTailscaleLogin() *tailcfg.Login { | ||||
| 	login := tailcfg.Login{ | ||||
| 		ID:            tailcfg.LoginID(n.ID), | ||||
| 		LoginName:     n.Name, | ||||
| @ -215,24 +215,24 @@ func (h *Headscale) getMapResponseUserProfiles( | ||||
| 	machine Machine, | ||||
| 	peers Machines, | ||||
| ) []tailcfg.UserProfile { | ||||
| 	namespaceMap := make(map[string]Namespace) | ||||
| 	namespaceMap[machine.Namespace.Name] = machine.Namespace | ||||
| 	userMap := make(map[string]User) | ||||
| 	userMap[machine.User.Name] = machine.User | ||||
| 	for _, peer := range peers { | ||||
| 		namespaceMap[peer.Namespace.Name] = peer.Namespace // not worth checking if already is there | ||||
| 		userMap[peer.User.Name] = peer.User // not worth checking if already is there | ||||
| 	} | ||||
| 
 | ||||
| 	profiles := []tailcfg.UserProfile{} | ||||
| 	for _, namespace := range namespaceMap { | ||||
| 		displayName := namespace.Name | ||||
| 	for _, user := range userMap { | ||||
| 		displayName := user.Name | ||||
| 
 | ||||
| 		if h.cfg.BaseDomain != "" { | ||||
| 			displayName = fmt.Sprintf("%s@%s", namespace.Name, h.cfg.BaseDomain) | ||||
| 			displayName = fmt.Sprintf("%s@%s", user.Name, h.cfg.BaseDomain) | ||||
| 		} | ||||
| 
 | ||||
| 		profiles = append(profiles, | ||||
| 			tailcfg.UserProfile{ | ||||
| 				ID:          tailcfg.UserID(namespace.ID), | ||||
| 				LoginName:   namespace.Name, | ||||
| 				ID:          tailcfg.UserID(user.ID), | ||||
| 				LoginName:   user.Name, | ||||
| 				DisplayName: displayName, | ||||
| 			}) | ||||
| 	} | ||||
| @ -240,16 +240,16 @@ func (h *Headscale) getMapResponseUserProfiles( | ||||
| 	return profiles | ||||
| } | ||||
| 
 | ||||
| func (n *Namespace) toProto() *v1.Namespace { | ||||
| 	return &v1.Namespace{ | ||||
| func (n *User) toProto() *v1.User { | ||||
| 	return &v1.User{ | ||||
| 		Id:        strconv.FormatUint(uint64(n.ID), Base10), | ||||
| 		Name:      n.Name, | ||||
| 		CreatedAt: timestamppb.New(n.CreatedAt), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NormalizeToFQDNRules will replace forbidden chars in namespace | ||||
| // it can also return an error if the namespace doesn't respect RFC 952 and 1123. | ||||
| // NormalizeToFQDNRules will replace forbidden chars in user | ||||
| // it can also return an error if the user doesn't respect RFC 952 and 1123. | ||||
| func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) { | ||||
| 	name = strings.ToLower(name) | ||||
| 	name = strings.ReplaceAll(name, "'", "") | ||||
| @ -259,14 +259,14 @@ func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) { | ||||
| 	} else { | ||||
| 		name = strings.ReplaceAll(name, "@", ".") | ||||
| 	} | ||||
| 	name = invalidCharsInNamespaceRegex.ReplaceAllString(name, "-") | ||||
| 	name = invalidCharsInUserRegex.ReplaceAllString(name, "-") | ||||
| 
 | ||||
| 	for _, elt := range strings.Split(name, ".") { | ||||
| 		if len(elt) > labelHostnameLength { | ||||
| 			return "", fmt.Errorf( | ||||
| 				"label %v is more than 63 chars: %w", | ||||
| 				elt, | ||||
| 				ErrInvalidNamespaceName, | ||||
| 				ErrInvalidUserName, | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
| @ -279,21 +279,21 @@ func CheckForFQDNRules(name string) error { | ||||
| 		return fmt.Errorf( | ||||
| 			"DNS segment must not be over 63 chars. %v doesn't comply with this rule: %w", | ||||
| 			name, | ||||
| 			ErrInvalidNamespaceName, | ||||
| 			ErrInvalidUserName, | ||||
| 		) | ||||
| 	} | ||||
| 	if strings.ToLower(name) != name { | ||||
| 		return fmt.Errorf( | ||||
| 			"DNS segment should be lowercase. %v doesn't comply with this rule: %w", | ||||
| 			name, | ||||
| 			ErrInvalidNamespaceName, | ||||
| 			ErrInvalidUserName, | ||||
| 		) | ||||
| 	} | ||||
| 	if invalidCharsInNamespaceRegex.MatchString(name) { | ||||
| 	if invalidCharsInUserRegex.MatchString(name) { | ||||
| 		return fmt.Errorf( | ||||
| 			"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w", | ||||
| 			name, | ||||
| 			ErrInvalidNamespaceName, | ||||
| 			ErrInvalidUserName, | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -8,43 +8,43 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| func (s *Suite) TestCreateAndDestroyUser(c *check.C) { | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(namespace.Name, check.Equals, "test") | ||||
| 	c.Assert(user.Name, check.Equals, "test") | ||||
| 
 | ||||
| 	namespaces, err := app.ListNamespaces() | ||||
| 	users, err := app.ListUsers() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(len(namespaces), check.Equals, 1) | ||||
| 	c.Assert(len(users), check.Equals, 1) | ||||
| 
 | ||||
| 	err = app.DestroyNamespace("test") | ||||
| 	err = app.DestroyUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetNamespace("test") | ||||
| 	_, err = app.GetUser("test") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestDestroyNamespaceErrors(c *check.C) { | ||||
| 	err := app.DestroyNamespace("test") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceNotFound) | ||||
| func (s *Suite) TestDestroyUserErrors(c *check.C) { | ||||
| 	err := app.DestroyUser("test") | ||||
| 	c.Assert(err, check.Equals, ErrUserNotFound) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	err = app.DestroyNamespace("test") | ||||
| 	err = app.DestroyUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	result := app.db.Preload("Namespace").First(&pak, "key = ?", pak.Key) | ||||
| 	// destroying a namespace also deletes all associated preauthkeys | ||||
| 	result := app.db.Preload("User").First(&pak, "key = ?", pak.Key) | ||||
| 	// destroying a user also deletes all associated preauthkeys | ||||
| 	c.Assert(result.Error, check.Equals, gorm.ErrRecordNotFound) | ||||
| 
 | ||||
| 	namespace, err = app.CreateNamespace("test") | ||||
| 	user, err = app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err = app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err = app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine := Machine{ | ||||
| @ -53,57 +53,57 @@ func (s *Suite) TestDestroyNamespaceErrors(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| 
 | ||||
| 	err = app.DestroyNamespace("test") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceNotEmptyOfNodes) | ||||
| 	err = app.DestroyUser("test") | ||||
| 	c.Assert(err, check.Equals, ErrUserStillHasNodes) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestRenameNamespace(c *check.C) { | ||||
| 	namespaceTest, err := app.CreateNamespace("test") | ||||
| func (s *Suite) TestRenameUser(c *check.C) { | ||||
| 	userTest, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(namespaceTest.Name, check.Equals, "test") | ||||
| 	c.Assert(userTest.Name, check.Equals, "test") | ||||
| 
 | ||||
| 	namespaces, err := app.ListNamespaces() | ||||
| 	users, err := app.ListUsers() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(len(namespaces), check.Equals, 1) | ||||
| 	c.Assert(len(users), check.Equals, 1) | ||||
| 
 | ||||
| 	err = app.RenameNamespace("test", "test-renamed") | ||||
| 	err = app.RenameUser("test", "test-renamed") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetNamespace("test") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceNotFound) | ||||
| 	_, err = app.GetUser("test") | ||||
| 	c.Assert(err, check.Equals, ErrUserNotFound) | ||||
| 
 | ||||
| 	_, err = app.GetNamespace("test-renamed") | ||||
| 	_, err = app.GetUser("test-renamed") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	err = app.RenameNamespace("test-does-not-exit", "test") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceNotFound) | ||||
| 	err = app.RenameUser("test-does-not-exit", "test") | ||||
| 	c.Assert(err, check.Equals, ErrUserNotFound) | ||||
| 
 | ||||
| 	namespaceTest2, err := app.CreateNamespace("test2") | ||||
| 	userTest2, err := app.CreateUser("test2") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(namespaceTest2.Name, check.Equals, "test2") | ||||
| 	c.Assert(userTest2.Name, check.Equals, "test2") | ||||
| 
 | ||||
| 	err = app.RenameNamespace("test2", "test-renamed") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceExists) | ||||
| 	err = app.RenameUser("test2", "test-renamed") | ||||
| 	c.Assert(err, check.Equals, ErrUserExists) | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 	namespaceShared1, err := app.CreateNamespace("shared1") | ||||
| 	userShared1, err := app.CreateUser("shared1") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared2, err := app.CreateNamespace("shared2") | ||||
| 	userShared2, err := app.CreateUser("shared2") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespaceShared3, err := app.CreateNamespace("shared3") | ||||
| 	userShared3, err := app.CreateUser("shared3") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyShared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -112,7 +112,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyShared2, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared2.Name, | ||||
| 		userShared2.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -121,7 +121,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKeyShared3, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared3.Name, | ||||
| 		userShared3.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -130,7 +130,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	preAuthKey2Shared1, err := app.CreatePreAuthKey( | ||||
| 		namespaceShared1.Name, | ||||
| 		userShared1.Name, | ||||
| 		false, | ||||
| 		false, | ||||
| 		nil, | ||||
| @ -138,7 +138,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, "test_get_shared_nodes_1") | ||||
| 	_, err = app.GetMachine(userShared1.Name, "test_get_shared_nodes_1") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	machineInShared1 := &Machine{ | ||||
| @ -147,15 +147,15 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		NodeKey:        "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		DiscoKey:       "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66", | ||||
| 		Hostname:       "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared1.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared1) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared1.Name, machineInShared1.Hostname) | ||||
| 	_, err = app.GetMachine(userShared1.Name, machineInShared1.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared2 := &Machine{ | ||||
| @ -164,15 +164,15 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		UserID:    userShared2.ID, | ||||
| 		User:      *userShared2, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared2.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared2) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared2.Name, machineInShared2.Hostname) | ||||
| 	_, err = app.GetMachine(userShared2.Name, machineInShared2.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineInShared3 := &Machine{ | ||||
| @ -181,15 +181,15 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		UserID:    userShared3.ID, | ||||
| 		User:      *userShared3, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared3.ID), | ||||
| 	} | ||||
| 	app.db.Save(machineInShared3) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespaceShared3.Name, machineInShared3.Hostname) | ||||
| 	_, err = app.GetMachine(userShared3.Name, machineInShared3.Hostname) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine2InShared1 := &Machine{ | ||||
| @ -198,8 +198,8 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		NodeKey:        "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		DiscoKey:       "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863", | ||||
| 		Hostname:       "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		UserID:    userShared1.ID, | ||||
| 		User:      *userShared1, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netip.Addr{netip.MustParseAddr("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2Shared1.ID), | ||||
| @ -218,7 +218,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 
 | ||||
| 	found := false | ||||
| 	for _, userProfiles := range userProfiles { | ||||
| 		if userProfiles.DisplayName == namespaceShared1.Name { | ||||
| 		if userProfiles.DisplayName == userShared1.Name { | ||||
| 			found = true | ||||
| 
 | ||||
| 			break | ||||
| @ -228,7 +228,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 
 | ||||
| 	found = false | ||||
| 	for _, userProfile := range userProfiles { | ||||
| 		if userProfile.DisplayName == namespaceShared2.Name { | ||||
| 		if userProfile.DisplayName == userShared2.Name { | ||||
| 			found = true | ||||
| 
 | ||||
| 			break | ||||
| @ -294,7 +294,7 @@ func TestNormalizeToFQDNRules(t *testing.T) { | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "namespace name with space", | ||||
| 			name: "user name with space", | ||||
| 			args: args{ | ||||
| 				name:             "name space", | ||||
| 				stripEmailDomain: false, | ||||
| @ -303,7 +303,7 @@ func TestNormalizeToFQDNRules(t *testing.T) { | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "namespace with quote", | ||||
| 			name: "user with quote", | ||||
| 			args: args{ | ||||
| 				name:             "Jamie's iPhone 5", | ||||
| 				stripEmailDomain: false, | ||||
| @ -341,29 +341,29 @@ func TestCheckForFQDNRules(t *testing.T) { | ||||
| 		wantErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "valid: namespace", | ||||
| 			args:    args{name: "valid-namespace"}, | ||||
| 			name:    "valid: user", | ||||
| 			args:    args{name: "valid-user"}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "invalid: capitalized namespace", | ||||
| 			args:    args{name: "Invalid-CapItaLIzed-namespace"}, | ||||
| 			name:    "invalid: capitalized user", | ||||
| 			args:    args{name: "Invalid-CapItaLIzed-user"}, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "invalid: email as namespace", | ||||
| 			name:    "invalid: email as user", | ||||
| 			args:    args{name: "foo.bar@example.com"}, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "invalid: chars in namespace name", | ||||
| 			args:    args{name: "super-namespace+name"}, | ||||
| 			name:    "invalid: chars in user name", | ||||
| 			args:    args{name: "super-user+name"}, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid: too long name for namespace", | ||||
| 			name: "invalid: too long name for user", | ||||
| 			args: args{ | ||||
| 				name: "super-long-namespace-name-that-should-be-a-little-more-than-63-chars", | ||||
| 				name: "super-long-user-name-that-should-be-a-little-more-than-63-chars", | ||||
| 			}, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| @ -377,14 +377,14 @@ func TestCheckForFQDNRules(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestSetMachineNamespace(c *check.C) { | ||||
| 	oldNamespace, err := app.CreateNamespace("old") | ||||
| func (s *Suite) TestSetMachineUser(c *check.C) { | ||||
| 	oldUser, err := app.CreateUser("old") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	newNamespace, err := app.CreateNamespace("new") | ||||
| 	newUser, err := app.CreateUser("new") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(oldNamespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(oldUser.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine := Machine{ | ||||
| @ -393,23 +393,23 @@ func (s *Suite) TestSetMachineNamespace(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    oldNamespace.ID, | ||||
| 		UserID:    oldUser.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 	app.db.Save(&machine) | ||||
| 	c.Assert(machine.NamespaceID, check.Equals, oldNamespace.ID) | ||||
| 	c.Assert(machine.UserID, check.Equals, oldUser.ID) | ||||
| 
 | ||||
| 	err = app.SetMachineNamespace(&machine, newNamespace.Name) | ||||
| 	err = app.SetMachineUser(&machine, newUser.Name) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(machine.NamespaceID, check.Equals, newNamespace.ID) | ||||
| 	c.Assert(machine.Namespace.Name, check.Equals, newNamespace.Name) | ||||
| 	c.Assert(machine.UserID, check.Equals, newUser.ID) | ||||
| 	c.Assert(machine.User.Name, check.Equals, newUser.Name) | ||||
| 
 | ||||
| 	err = app.SetMachineNamespace(&machine, "non-existing-namespace") | ||||
| 	c.Assert(err, check.Equals, ErrNamespaceNotFound) | ||||
| 	err = app.SetMachineUser(&machine, "non-existing-user") | ||||
| 	c.Assert(err, check.Equals, ErrUserNotFound) | ||||
| 
 | ||||
| 	err = app.SetMachineNamespace(&machine, newNamespace.Name) | ||||
| 	err = app.SetMachineUser(&machine, newUser.Name) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(machine.NamespaceID, check.Equals, newNamespace.ID) | ||||
| 	c.Assert(machine.Namespace.Name, check.Equals, newNamespace.Name) | ||||
| 	c.Assert(machine.UserID, check.Equals, newUser.ID) | ||||
| 	c.Assert(machine.User.Name, check.Equals, newUser.Name) | ||||
| } | ||||
|  | ||||
							
								
								
									
										42
									
								
								oidc.go
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								oidc.go
									
									
									
									
									
								
							| @ -171,7 +171,7 @@ var oidcCallbackTemplate = template.Must( | ||||
| ) | ||||
| 
 | ||||
| // OIDCCallback handles the callback from the OIDC endpoint | ||||
| // Retrieves the nkey from the state cache and adds the machine to the users email namespace | ||||
| // Retrieves the nkey from the state cache and adds the machine to the users email user | ||||
| // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities | ||||
| // TODO: Add groups information from OIDC tokens into machine HostInfo | ||||
| // Listens in /oidc/callback. | ||||
| @ -223,7 +223,7 @@ func (h *Headscale) OIDCCallback( | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	namespaceName, err := getNamespaceName(writer, claims, h.cfg.OIDC.StripEmaildomain) | ||||
| 	userName, err := getUserName(writer, claims, h.cfg.OIDC.StripEmaildomain) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| @ -231,12 +231,12 @@ func (h *Headscale) OIDCCallback( | ||||
| 	// register the machine if it's new | ||||
| 	log.Debug().Msg("Registering new machine after successful callback") | ||||
| 
 | ||||
| 	namespace, err := h.findOrCreateNewNamespaceForOIDCCallback(writer, namespaceName) | ||||
| 	user, err := h.findOrCreateNewUserForOIDCCallback(writer, userName) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err := h.registerMachineForOIDCCallback(writer, namespace, nodeKey, idToken.Expiry); err != nil { | ||||
| 	if err := h.registerMachineForOIDCCallback(writer, user, nodeKey, idToken.Expiry); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| @ -606,12 +606,12 @@ func (h *Headscale) validateMachineForOIDCCallback( | ||||
| 	return &nodeKey, false, nil | ||||
| } | ||||
| 
 | ||||
| func getNamespaceName( | ||||
| func getUserName( | ||||
| 	writer http.ResponseWriter, | ||||
| 	claims *IDTokenClaims, | ||||
| 	stripEmaildomain bool, | ||||
| ) (string, error) { | ||||
| 	namespaceName, err := NormalizeToFQDNRules( | ||||
| 	userName, err := NormalizeToFQDNRules( | ||||
| 		claims.Email, | ||||
| 		stripEmaildomain, | ||||
| 	) | ||||
| @ -630,25 +630,25 @@ func getNamespaceName( | ||||
| 		return "", err | ||||
| 	} | ||||
| 
 | ||||
| 	return namespaceName, nil | ||||
| 	return userName, nil | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) findOrCreateNewNamespaceForOIDCCallback( | ||||
| func (h *Headscale) findOrCreateNewUserForOIDCCallback( | ||||
| 	writer http.ResponseWriter, | ||||
| 	namespaceName string, | ||||
| ) (*Namespace, error) { | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| 	if errors.Is(err, ErrNamespaceNotFound) { | ||||
| 		namespace, err = h.CreateNamespace(namespaceName) | ||||
| 	userName string, | ||||
| ) (*User, error) { | ||||
| 	user, err := h.GetUser(userName) | ||||
| 	if errors.Is(err, ErrUserNotFound) { | ||||
| 		user, err = h.CreateUser(userName) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Err(err). | ||||
| 				Caller(). | ||||
| 				Msgf("could not create new namespace '%s'", namespaceName) | ||||
| 				Msgf("could not create new user '%s'", userName) | ||||
| 			writer.Header().Set("Content-Type", "text/plain; charset=utf-8") | ||||
| 			writer.WriteHeader(http.StatusInternalServerError) | ||||
| 			_, werr := writer.Write([]byte("could not create namespace")) | ||||
| 			_, werr := writer.Write([]byte("could not create user")) | ||||
| 			if werr != nil { | ||||
| 				log.Error(). | ||||
| 					Caller(). | ||||
| @ -662,11 +662,11 @@ func (h *Headscale) findOrCreateNewNamespaceForOIDCCallback( | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Str("namespace", namespaceName). | ||||
| 			Msg("could not find or create namespace") | ||||
| 			Str("user", userName). | ||||
| 			Msg("could not find or create user") | ||||
| 		writer.Header().Set("Content-Type", "text/plain; charset=utf-8") | ||||
| 		writer.WriteHeader(http.StatusInternalServerError) | ||||
| 		_, werr := writer.Write([]byte("could not find or create namespace")) | ||||
| 		_, werr := writer.Write([]byte("could not find or create user")) | ||||
| 		if werr != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| @ -677,18 +677,18 @@ func (h *Headscale) findOrCreateNewNamespaceForOIDCCallback( | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	return namespace, nil | ||||
| 	return user, nil | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) registerMachineForOIDCCallback( | ||||
| 	writer http.ResponseWriter, | ||||
| 	namespace *Namespace, | ||||
| 	user *User, | ||||
| 	nodeKey *key.NodePublic, | ||||
| 	expiry time.Time, | ||||
| ) error { | ||||
| 	if _, err := h.RegisterMachineFromAuthCallback( | ||||
| 		nodeKey.String(), | ||||
| 		namespace.Name, | ||||
| 		user.Name, | ||||
| 		&expiry, | ||||
| 		RegisterMethodOIDC, | ||||
| 	); err != nil { | ||||
|  | ||||
| @ -18,16 +18,16 @@ const ( | ||||
| 	ErrPreAuthKeyNotFound          = Error("AuthKey not found") | ||||
| 	ErrPreAuthKeyExpired           = Error("AuthKey expired") | ||||
| 	ErrSingleUseAuthKeyHasBeenUsed = Error("AuthKey has already been used") | ||||
| 	ErrNamespaceMismatch           = Error("namespace mismatch") | ||||
| 	ErrUserMismatch           = Error("user mismatch") | ||||
| 	ErrPreAuthKeyACLTagInvalid     = Error("AuthKey tag is invalid") | ||||
| ) | ||||
| 
 | ||||
| // PreAuthKey describes a pre-authorization key usable in a particular namespace. | ||||
| // PreAuthKey describes a pre-authorization key usable in a particular user. | ||||
| type PreAuthKey struct { | ||||
| 	ID          uint64 `gorm:"primary_key"` | ||||
| 	Key         string | ||||
| 	NamespaceID uint | ||||
| 	Namespace   Namespace | ||||
| 	UserID uint | ||||
| 	User   User | ||||
| 	Reusable    bool | ||||
| 	Ephemeral   bool `gorm:"default:false"` | ||||
| 	Used        bool `gorm:"default:false"` | ||||
| @ -44,15 +44,15 @@ type PreAuthKeyACLTag struct { | ||||
| 	Tag          string | ||||
| } | ||||
| 
 | ||||
| // CreatePreAuthKey creates a new PreAuthKey in a namespace, and returns it. | ||||
| // CreatePreAuthKey creates a new PreAuthKey in a user, and returns it. | ||||
| func (h *Headscale) CreatePreAuthKey( | ||||
| 	namespaceName string, | ||||
| 	userName string, | ||||
| 	reusable bool, | ||||
| 	ephemeral bool, | ||||
| 	expiration *time.Time, | ||||
| 	aclTags []string, | ||||
| ) (*PreAuthKey, error) { | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| 	user, err := h.GetUser(userName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -71,8 +71,8 @@ func (h *Headscale) CreatePreAuthKey( | ||||
| 
 | ||||
| 	key := PreAuthKey{ | ||||
| 		Key:         kstr, | ||||
| 		NamespaceID: namespace.ID, | ||||
| 		Namespace:   *namespace, | ||||
| 		UserID: user.ID, | ||||
| 		User:   *user, | ||||
| 		Reusable:    reusable, | ||||
| 		Ephemeral:   ephemeral, | ||||
| 		CreatedAt:   &now, | ||||
| @ -110,15 +110,15 @@ func (h *Headscale) CreatePreAuthKey( | ||||
| 	return &key, nil | ||||
| } | ||||
| 
 | ||||
| // ListPreAuthKeys returns the list of PreAuthKeys for a namespace. | ||||
| func (h *Headscale) ListPreAuthKeys(namespaceName string) ([]PreAuthKey, error) { | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| // ListPreAuthKeys returns the list of PreAuthKeys for a user. | ||||
| func (h *Headscale) ListPreAuthKeys(userName string) ([]PreAuthKey, error) { | ||||
| 	user, err := h.GetUser(userName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	keys := []PreAuthKey{} | ||||
| 	if err := h.db.Preload("Namespace").Preload("ACLTags").Where(&PreAuthKey{NamespaceID: namespace.ID}).Find(&keys).Error; err != nil { | ||||
| 	if err := h.db.Preload("User").Preload("ACLTags").Where(&PreAuthKey{UserID: user.ID}).Find(&keys).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| @ -126,14 +126,14 @@ func (h *Headscale) ListPreAuthKeys(namespaceName string) ([]PreAuthKey, error) | ||||
| } | ||||
| 
 | ||||
| // GetPreAuthKey returns a PreAuthKey for a given key. | ||||
| func (h *Headscale) GetPreAuthKey(namespace string, key string) (*PreAuthKey, error) { | ||||
| func (h *Headscale) GetPreAuthKey(user string, key string) (*PreAuthKey, error) { | ||||
| 	pak, err := h.checkKeyValidity(key) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if pak.Namespace.Name != namespace { | ||||
| 		return nil, ErrNamespaceMismatch | ||||
| 	if pak.User.Name != user { | ||||
| 		return nil, ErrUserMismatch | ||||
| 	} | ||||
| 
 | ||||
| 	return pak, nil | ||||
| @ -178,7 +178,7 @@ func (h *Headscale) UsePreAuthKey(k *PreAuthKey) error { | ||||
| // If returns no error and a PreAuthKey, it can be used. | ||||
| func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) { | ||||
| 	pak := PreAuthKey{} | ||||
| 	if result := h.db.Preload("Namespace").Preload("ACLTags").First(&pak, "key = ?", k); errors.Is( | ||||
| 	if result := h.db.Preload("User").Preload("ACLTags").First(&pak, "key = ?", k); errors.Is( | ||||
| 		result.Error, | ||||
| 		gorm.ErrRecordNotFound, | ||||
| 	) { | ||||
| @ -217,7 +217,7 @@ func (h *Headscale) generateKey() (string, error) { | ||||
| 
 | ||||
| func (key *PreAuthKey) toProto() *v1.PreAuthKey { | ||||
| 	protoKey := v1.PreAuthKey{ | ||||
| 		Namespace: key.Namespace.Name, | ||||
| 		User: key.User.Name, | ||||
| 		Id:        strconv.FormatUint(key.ID, Base10), | ||||
| 		Key:       key.Key, | ||||
| 		Ephemeral: key.Ephemeral, | ||||
|  | ||||
| @ -11,36 +11,36 @@ func (*Suite) TestCreatePreAuthKey(c *check.C) { | ||||
| 
 | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	key, err := app.CreatePreAuthKey(namespace.Name, true, false, nil, nil) | ||||
| 	key, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	// Did we get a valid key? | ||||
| 	c.Assert(key.Key, check.NotNil) | ||||
| 	c.Assert(len(key.Key), check.Equals, 48) | ||||
| 
 | ||||
| 	// Make sure the Namespace association is populated | ||||
| 	c.Assert(key.Namespace.Name, check.Equals, namespace.Name) | ||||
| 	// Make sure the User association is populated | ||||
| 	c.Assert(key.User.Name, check.Equals, user.Name) | ||||
| 
 | ||||
| 	_, err = app.ListPreAuthKeys("bogus") | ||||
| 	c.Assert(err, check.NotNil) | ||||
| 
 | ||||
| 	keys, err := app.ListPreAuthKeys(namespace.Name) | ||||
| 	keys, err := app.ListPreAuthKeys(user.Name) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(len(keys), check.Equals, 1) | ||||
| 
 | ||||
| 	// Make sure the Namespace association is populated | ||||
| 	c.Assert((keys)[0].Namespace.Name, check.Equals, namespace.Name) | ||||
| 	// Make sure the User association is populated | ||||
| 	c.Assert((keys)[0].User.Name, check.Equals, user.Name) | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestExpiredPreAuthKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test2") | ||||
| 	user, err := app.CreateUser("test2") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	now := time.Now() | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, true, false, &now, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, true, false, &now, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	key, err := app.checkKeyValidity(pak.Key) | ||||
| @ -55,10 +55,10 @@ func (*Suite) TestPreAuthKeyDoesNotExist(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestValidateKeyOk(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test3") | ||||
| 	user, err := app.CreateUser("test3") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, true, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	key, err := app.checkKeyValidity(pak.Key) | ||||
| @ -67,10 +67,10 @@ func (*Suite) TestValidateKeyOk(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestAlreadyUsedKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test4") | ||||
| 	user, err := app.CreateUser("test4") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine := Machine{ | ||||
| @ -79,7 +79,7 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -91,10 +91,10 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestReusableBeingUsedKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test5") | ||||
| 	user, err := app.CreateUser("test5") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, true, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machine := Machine{ | ||||
| @ -103,7 +103,7 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -115,10 +115,10 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test6") | ||||
| 	user, err := app.CreateUser("test6") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	key, err := app.checkKeyValidity(pak.Key) | ||||
| @ -127,10 +127,10 @@ func (*Suite) TestNotReusableNotBeingUsedKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestEphemeralKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test7") | ||||
| 	user, err := app.CreateUser("test7") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, true, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, true, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	now := time.Now() | ||||
| @ -140,7 +140,7 @@ func (*Suite) TestEphemeralKey(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		LastSeen:       &now, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| @ -162,10 +162,10 @@ func (*Suite) TestEphemeralKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestExpirePreauthKey(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test3") | ||||
| 	user, err := app.CreateUser("test3") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, true, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, true, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(pak.Expiration, check.IsNil) | ||||
| 
 | ||||
| @ -179,10 +179,10 @@ func (*Suite) TestExpirePreauthKey(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test6") | ||||
| 	user, err := app.CreateUser("test6") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	pak.Used = true | ||||
| 	app.db.Save(&pak) | ||||
| @ -192,15 +192,15 @@ func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (*Suite) TestPreAuthKeyACLTags(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test8") | ||||
| 	user, err := app.CreateUser("test8") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.CreatePreAuthKey(namespace.Name, false, false, nil, []string{"badtag"}) | ||||
| 	_, err = app.CreatePreAuthKey(user.Name, false, false, nil, []string{"badtag"}) | ||||
| 	c.Assert(err, check.NotNil) // Confirm that malformed tags are rejected | ||||
| 
 | ||||
| 	tags := []string{"tag:test1", "tag:test2"} | ||||
| 	tagsWithDuplicate := []string{"tag:test1", "tag:test2", "tag:test2"} | ||||
| 	_, err = app.CreatePreAuthKey(namespace.Name, false, false, nil, tagsWithDuplicate) | ||||
| 	_, err = app.CreatePreAuthKey(user.Name, false, false, nil, tagsWithDuplicate) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	listedPaks, err := app.ListPreAuthKeys("test8") | ||||
|  | ||||
| @ -325,7 +325,7 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 				Err(err). | ||||
| 				Msg("Cannot encode message") | ||||
| 			http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.User.Name). | ||||
| 				Inc() | ||||
| 
 | ||||
| 			return | ||||
| @ -350,7 +350,7 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 			Msg("Failed authentication via AuthKey") | ||||
| 
 | ||||
| 		if pak != nil { | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.User.Name). | ||||
| 				Inc() | ||||
| 		} else { | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", "unknown").Inc() | ||||
| @ -428,7 +428,7 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 		machineToRegister := Machine{ | ||||
| 			Hostname:       registerRequest.Hostinfo.Hostname, | ||||
| 			GivenName:      givenName, | ||||
| 			NamespaceID:    pak.Namespace.ID, | ||||
| 			UserID:    pak.User.ID, | ||||
| 			MachineKey:     MachinePublicKeyStripPrefix(machineKey), | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			Expiry:         ®isterRequest.Expiry, | ||||
| @ -447,7 +447,7 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 				Bool("noise", isNoise). | ||||
| 				Err(err). | ||||
| 				Msg("could not register machine") | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.User.Name). | ||||
| 				Inc() | ||||
| 			http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 
 | ||||
| @ -462,7 +462,7 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 			Bool("noise", isNoise). | ||||
| 			Err(err). | ||||
| 			Msg("Failed to use pre-auth key") | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.User.Name). | ||||
| 			Inc() | ||||
| 		http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 
 | ||||
| @ -470,10 +470,10 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 	} | ||||
| 
 | ||||
| 	resp.MachineAuthorized = true | ||||
| 	resp.User = *pak.Namespace.toUser() | ||||
| 	resp.User = *pak.User.toTailscaleUser() | ||||
| 	// Provide LoginName when registering with pre-auth key | ||||
| 	// Otherwise it will need to exec `tailscale up` twice to fetch the *LoginName* | ||||
| 	resp.Login = *pak.Namespace.toLogin() | ||||
| 	resp.Login = *pak.User.toTailscaleLogin() | ||||
| 
 | ||||
| 	respBody, err := h.marshalResponse(resp, machineKey, isNoise) | ||||
| 	if err != nil { | ||||
| @ -484,13 +484,13 @@ func (h *Headscale) handleAuthKeyCommon( | ||||
| 			Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 			Err(err). | ||||
| 			Msg("Cannot encode message") | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.User.Name). | ||||
| 			Inc() | ||||
| 		http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 	machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name). | ||||
| 	machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.User.Name). | ||||
| 		Inc() | ||||
| 	writer.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||
| 	writer.WriteHeader(http.StatusOK) | ||||
| @ -600,7 +600,7 @@ func (h *Headscale) handleMachineLogOutCommon( | ||||
| 	resp.AuthURL = "" | ||||
| 	resp.MachineAuthorized = false | ||||
| 	resp.NodeKeyExpired = true | ||||
| 	resp.User = *machine.Namespace.toUser() | ||||
| 	resp.User = *machine.User.toTailscaleUser() | ||||
| 	respBody, err := h.marshalResponse(resp, machineKey, isNoise) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| @ -662,8 +662,8 @@ func (h *Headscale) handleMachineValidRegistrationCommon( | ||||
| 
 | ||||
| 	resp.AuthURL = "" | ||||
| 	resp.MachineAuthorized = true | ||||
| 	resp.User = *machine.Namespace.toUser() | ||||
| 	resp.Login = *machine.Namespace.toLogin() | ||||
| 	resp.User = *machine.User.toTailscaleUser() | ||||
| 	resp.Login = *machine.User.toTailscaleLogin() | ||||
| 
 | ||||
| 	respBody, err := h.marshalResponse(resp, machineKey, isNoise) | ||||
| 	if err != nil { | ||||
| @ -672,13 +672,13 @@ func (h *Headscale) handleMachineValidRegistrationCommon( | ||||
| 			Bool("noise", isNoise). | ||||
| 			Err(err). | ||||
| 			Msg("Cannot encode message") | ||||
| 		machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("update", "web", "error", machine.User.Name). | ||||
| 			Inc() | ||||
| 		http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 	machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name). | ||||
| 	machineRegistrations.WithLabelValues("update", "web", "success", machine.User.Name). | ||||
| 		Inc() | ||||
| 
 | ||||
| 	writer.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||
| @ -726,7 +726,7 @@ func (h *Headscale) handleMachineRefreshKeyCommon( | ||||
| 	} | ||||
| 
 | ||||
| 	resp.AuthURL = "" | ||||
| 	resp.User = *machine.Namespace.toUser() | ||||
| 	resp.User = *machine.User.toTailscaleUser() | ||||
| 	respBody, err := h.marshalResponse(resp, machineKey, isNoise) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| @ -801,13 +801,13 @@ func (h *Headscale) handleMachineExpiredOrLoggedOutCommon( | ||||
| 			Bool("noise", isNoise). | ||||
| 			Err(err). | ||||
| 			Msg("Cannot encode message") | ||||
| 		machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("reauth", "web", "error", machine.User.Name). | ||||
| 			Inc() | ||||
| 		http.Error(writer, "Internal server error", http.StatusInternalServerError) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 	machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name). | ||||
| 	machineRegistrations.WithLabelValues("reauth", "web", "success", machine.User.Name). | ||||
| 		Inc() | ||||
| 
 | ||||
| 	writer.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||
|  | ||||
| @ -183,7 +183,7 @@ func (h *Headscale) handlePollCommon( | ||||
| 		} | ||||
| 		// It sounds like we should update the nodes when we have received a endpoint update | ||||
| 		// even tho the comments in the tailscale code dont explicitly say so. | ||||
| 		updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "endpoint-update"). | ||||
| 		updateRequestsFromNode.WithLabelValues(machine.User.Name, machine.Hostname, "endpoint-update"). | ||||
| 			Inc() | ||||
| 		updateChan <- struct{}{} | ||||
| 
 | ||||
| @ -216,7 +216,7 @@ func (h *Headscale) handlePollCommon( | ||||
| 		Bool("noise", isNoise). | ||||
| 		Str("machine", machine.Hostname). | ||||
| 		Msg("Notifying peers") | ||||
| 	updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "full-update"). | ||||
| 	updateRequestsFromNode.WithLabelValues(machine.User.Name, machine.Hostname, "full-update"). | ||||
| 		Inc() | ||||
| 	updateChan <- struct{}{} | ||||
| 
 | ||||
| @ -342,7 +342,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 			now := time.Now().UTC() | ||||
| 			machine.LastSeen = &now | ||||
| 
 | ||||
| 			lastStateUpdate.WithLabelValues(machine.Namespace.Name, machine.Hostname). | ||||
| 			lastStateUpdate.WithLabelValues(machine.User.Name, machine.Hostname). | ||||
| 				Set(float64(now.Unix())) | ||||
| 			machine.LastSuccessfulUpdate = &now | ||||
| 
 | ||||
| @ -453,7 +453,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 				Str("machine", machine.Hostname). | ||||
| 				Str("channel", "update"). | ||||
| 				Msg("Received a request for update") | ||||
| 			updateRequestsReceivedOnChannel.WithLabelValues(machine.Namespace.Name, machine.Hostname). | ||||
| 			updateRequestsReceivedOnChannel.WithLabelValues(machine.User.Name, machine.Hostname). | ||||
| 				Inc() | ||||
| 
 | ||||
| 			if h.isOutdated(machine) { | ||||
| @ -466,7 +466,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 					Bool("noise", isNoise). | ||||
| 					Str("machine", machine.Hostname). | ||||
| 					Time("last_successful_update", lastUpdate). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.Namespace)). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.User)). | ||||
| 					Msgf("There has been updates since the last successful update to %s", machine.Hostname) | ||||
| 				data, err := h.getMapResponseData(mapRequest, machine, isNoise) | ||||
| 				if err != nil { | ||||
| @ -489,7 +489,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 						Str("channel", "update"). | ||||
| 						Err(err). | ||||
| 						Msg("Could not write the map response") | ||||
| 					updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "failed"). | ||||
| 					updateRequestsSentToNode.WithLabelValues(machine.User.Name, machine.Hostname, "failed"). | ||||
| 						Inc() | ||||
| 
 | ||||
| 					return | ||||
| @ -514,7 +514,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 					Str("machine", machine.Hostname). | ||||
| 					Str("channel", "update"). | ||||
| 					Msg("Updated Map has been sent") | ||||
| 				updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "success"). | ||||
| 				updateRequestsSentToNode.WithLabelValues(machine.User.Name, machine.Hostname, "success"). | ||||
| 					Inc() | ||||
| 
 | ||||
| 				// Keep track of the last successful update, | ||||
| @ -540,7 +540,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 				} | ||||
| 				now := time.Now().UTC() | ||||
| 
 | ||||
| 				lastStateUpdate.WithLabelValues(machine.Namespace.Name, machine.Hostname). | ||||
| 				lastStateUpdate.WithLabelValues(machine.User.Name, machine.Hostname). | ||||
| 					Set(float64(now.Unix())) | ||||
| 				machine.LastSuccessfulUpdate = &now | ||||
| 
 | ||||
| @ -566,7 +566,7 @@ func (h *Headscale) pollNetMapStream( | ||||
| 					Bool("noise", isNoise). | ||||
| 					Str("machine", machine.Hostname). | ||||
| 					Time("last_successful_update", lastUpdate). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.Namespace)). | ||||
| 					Time("last_state_change", h.getLastStateChange(machine.User)). | ||||
| 					Msgf("%s is up to date", machine.Hostname) | ||||
| 			} | ||||
| 
 | ||||
| @ -676,7 +676,7 @@ func (h *Headscale) scheduledPollWorker( | ||||
| 				Str("machine", machine.Hostname). | ||||
| 				Bool("noise", isNoise). | ||||
| 				Msg("Sending update request") | ||||
| 			updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "scheduled-update"). | ||||
| 			updateRequestsFromNode.WithLabelValues(machine.User.Name, machine.Hostname, "scheduled-update"). | ||||
| 				Inc() | ||||
| 			select { | ||||
| 			case updateChan <- struct{}{}: | ||||
|  | ||||
| @ -10,10 +10,10 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestGetRoutes(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "test_get_route_machine") | ||||
| @ -32,7 +32,7 @@ func (s *Suite) TestGetRoutes(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_get_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -54,10 +54,10 @@ func (s *Suite) TestGetRoutes(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetEnableRoutes(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "test_enable_route_machine") | ||||
| @ -83,7 +83,7 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -129,10 +129,10 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestIsUniquePrefix(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "test_enable_route_machine") | ||||
| @ -157,7 +157,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo1), | ||||
| @ -182,7 +182,7 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo2), | ||||
| @ -213,10 +213,10 @@ func (s *Suite) TestIsUniquePrefix(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestSubnetFailover(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "test_enable_route_machine") | ||||
| @ -243,7 +243,7 @@ func (s *Suite) TestSubnetFailover(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo1), | ||||
| @ -280,7 +280,7 @@ func (s *Suite) TestSubnetFailover(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo2), | ||||
| @ -358,10 +358,10 @@ func (s *Suite) TestSubnetFailover(c *check.C) { | ||||
| // including both the primary routes the node is responsible for, and the | ||||
| // exit node routes if enabled. | ||||
| func (s *Suite) TestAllowedIPRoutes(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	user, err := app.CreateUser("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "test_enable_route_machine") | ||||
| @ -402,7 +402,7 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) { | ||||
| 		NodeKey:        NodePublicKeyStripPrefix(nodeKey.Public()), | ||||
| 		DiscoKey:       DiscoPublicKeyStripPrefix(discoKey.Public()), | ||||
| 		Hostname:       "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo1), | ||||
|  | ||||
| @ -22,10 +22,10 @@ func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| 	ips, err := app.getAvailableIPs() | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test-ip") | ||||
| 	user, err := app.CreateUser("test-ip") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| @ -37,7 +37,7 @@ func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		IPAddresses:    ips, | ||||
| @ -64,7 +64,7 @@ func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| } | ||||
| 
 | ||||
| func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test-ip-multi") | ||||
| 	user, err := app.CreateUser("test-ip-multi") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	for index := 1; index <= 350; index++ { | ||||
| @ -73,7 +73,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 		ips, err := app.getAvailableIPs() | ||||
| 		c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 		pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 		pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 		c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 		_, err = app.GetMachine("test", "testmachine") | ||||
| @ -85,7 +85,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 			NodeKey:        "bar", | ||||
| 			DiscoKey:       "faa", | ||||
| 			Hostname:       "testmachine", | ||||
| 			NamespaceID:    namespace.ID, | ||||
| 			UserID:    user.ID, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(pak.ID), | ||||
| 			IPAddresses:    ips, | ||||
| @ -160,10 +160,10 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) { | ||||
| 	c.Assert(len(ips), check.Equals, 1) | ||||
| 	c.Assert(ips[0].String(), check.Equals, expected.String()) | ||||
| 
 | ||||
| 	namespace, err := app.CreateNamespace("test-ip") | ||||
| 	user, err := app.CreateUser("test-ip") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil, nil) | ||||
| 	pak, err := app.CreatePreAuthKey(user.Name, false, false, nil, nil) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine("test", "testmachine") | ||||
| @ -175,7 +175,7 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) { | ||||
| 		NodeKey:        "bar", | ||||
| 		DiscoKey:       "faa", | ||||
| 		Hostname:       "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		UserID:    user.ID, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user