mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Resolve merge conflict
This commit is contained in:
		
						commit
						5b169010be
					
				| @ -29,6 +29,7 @@ linters: | ||||
|     - wrapcheck | ||||
|     - dupl | ||||
|     - makezero | ||||
|     - maintidx | ||||
| 
 | ||||
|     # We might want to enable this, but it might be a lot of work | ||||
|     - cyclop | ||||
|  | ||||
							
								
								
									
										52
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -1,31 +1,37 @@ | ||||
| # CHANGELOG | ||||
| 
 | ||||
| **0.15.0 (2022-xx-xx):** | ||||
| ## 0.15.0 (2022-xx-xx) | ||||
| 
 | ||||
| **BREAKING**: | ||||
| **Note:** Take a backup of your database before upgrading. | ||||
| 
 | ||||
| ### BREAKING | ||||
| 
 | ||||
| - Boundaries between Namespaces has been removed and all nodes can communicate by default [#357](https://github.com/juanfont/headscale/pull/357) | ||||
|   - To limit access between nodes, use [ACLs](./docs/acls.md). | ||||
| 
 | ||||
| **Features**: | ||||
| ### Features | ||||
| 
 | ||||
| - Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359) | ||||
| - Users can now use emails in ACL's groups [#372](https://github.com/juanfont/headscale/issues/372) | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Fix a bug were the same IP could be assigned to multiple hosts if joined in quick succession [#346](https://github.com/juanfont/headscale/pull/346) | ||||
| - Simplify the code behind registration of machines [#366](https://github.com/juanfont/headscale/pull/366) | ||||
|   - Nodes are now only written to database if they are registrated successfully | ||||
| - Fix a limitation in the ACLs that prevented users to write rules with `*` as source [#374](https://github.com/juanfont/headscale/issues/374) | ||||
| 
 | ||||
| **0.14.0 (2022-02-24):** | ||||
| ## 0.14.0 (2022-02-24) | ||||
| 
 | ||||
| **UPCOMING BREAKING**: | ||||
| From the **next** version (`0.15.0`), all machines will be able to communicate regardless of | ||||
| **UPCOMING ### BREAKING | ||||
| From the **next\*\* version (`0.15.0`), all machines will be able to communicate regardless of | ||||
| if they are in the same namespace. This means that the behaviour currently limited to ACLs | ||||
| will become default. From version `0.15.0`, all limitation of communications must be done | ||||
| with ACLs. | ||||
| 
 | ||||
| This is a part of aligning `headscale`'s behaviour with Tailscale's upstream behaviour. | ||||
| 
 | ||||
| **BREAKING**: | ||||
| ### BREAKING | ||||
| 
 | ||||
| - ACLs have been rewritten to align with the bevaviour Tailscale Control Panel provides. **NOTE:** This is only active if you use ACLs | ||||
|   - Namespaces are now treated as Users | ||||
| @ -33,17 +39,17 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh | ||||
|   - Tags should now work correctly and adding a host to Headscale should now reload the rules. | ||||
|   - The documentation have a [fictional example](docs/acls.md) that should cover some use cases of the ACLs features | ||||
| 
 | ||||
| **Features**: | ||||
| ### Features | ||||
| 
 | ||||
| - Add support for configurable mTLS [docs](docs/tls.md#configuring-mutual-tls-authentication-mtls) [#297](https://github.com/juanfont/headscale/pull/297) | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Remove dependency on CGO (switch from CGO SQLite to pure Go) [#346](https://github.com/juanfont/headscale/pull/346) | ||||
| 
 | ||||
| **0.13.0 (2022-02-18):** | ||||
| 
 | ||||
| **Features**: | ||||
| ### Features | ||||
| 
 | ||||
| - Add IPv6 support to the prefix assigned to namespaces | ||||
| - Add API Key support | ||||
| @ -54,7 +60,7 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh | ||||
|   - `oidc.domain_map` option has been removed | ||||
|   - `strip_email_domain` option has been added (see [config-example.yaml](./config_example.yaml)) | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208) | ||||
| - Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314) | ||||
| @ -63,35 +69,35 @@ This is a part of aligning `headscale`'s behaviour with Tailscale's upstream beh | ||||
| 
 | ||||
| **0.12.4 (2022-01-29):** | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292) | ||||
| - Trim whitespace before reading Private Key from file [#289](https://github.com/juanfont/headscale/pull/289) | ||||
| - Add new command to generate a private key for `headscale` [#290](https://github.com/juanfont/headscale/pull/290) | ||||
| - Fixed issue where hosts deleted from control server may be written back to the database, as long as they are connected to the control server [#278](https://github.com/juanfont/headscale/pull/278) | ||||
| 
 | ||||
| **0.12.3 (2022-01-13):** | ||||
| ## 0.12.3 (2022-01-13) | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Added Alpine container [#270](https://github.com/juanfont/headscale/pull/270) | ||||
| - Minor updates in dependencies [#271](https://github.com/juanfont/headscale/pull/271) | ||||
| 
 | ||||
| **0.12.2 (2022-01-11):** | ||||
| ## 0.12.2 (2022-01-11) | ||||
| 
 | ||||
| Happy New Year! | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Fix Docker release [#258](https://github.com/juanfont/headscale/pull/258) | ||||
| - Rewrite main docs [#262](https://github.com/juanfont/headscale/pull/262) | ||||
| - Improve Docker docs [#263](https://github.com/juanfont/headscale/pull/263) | ||||
| 
 | ||||
| **0.12.1 (2021-12-24):** | ||||
| ## 0.12.1 (2021-12-24) | ||||
| 
 | ||||
| (We are skipping 0.12.0 to correct a mishap done weeks ago with the version tagging) | ||||
| 
 | ||||
| **BREAKING**: | ||||
| ### BREAKING | ||||
| 
 | ||||
| - Upgrade to Tailscale 1.18 [#229](https://github.com/juanfont/headscale/pull/229) | ||||
|   - This change requires a new format for private key, private keys are now generated automatically: | ||||
| @ -99,19 +105,19 @@ Happy New Year! | ||||
|     2. Restart `headscale`, a new key will be generated. | ||||
|     3. Restart all Tailscale clients to fetch the new key | ||||
| 
 | ||||
| **Changes**: | ||||
| ### Changes | ||||
| 
 | ||||
| - Unify configuration example [#197](https://github.com/juanfont/headscale/pull/197) | ||||
| - Add stricter linting and formatting [#223](https://github.com/juanfont/headscale/pull/223) | ||||
| 
 | ||||
| **Features**: | ||||
| ### Features | ||||
| 
 | ||||
| - Add gRPC and HTTP API (HTTP API is currently disabled) [#204](https://github.com/juanfont/headscale/pull/204) | ||||
| - Use gRPC between the CLI and the server [#206](https://github.com/juanfont/headscale/pull/206), [#212](https://github.com/juanfont/headscale/pull/212) | ||||
| - Beta OpenID Connect support [#126](https://github.com/juanfont/headscale/pull/126), [#227](https://github.com/juanfont/headscale/pull/227) | ||||
| 
 | ||||
| **0.11.0 (2021-10-25):** | ||||
| ## 0.11.0 (2021-10-25) | ||||
| 
 | ||||
| **BREAKING**: | ||||
| ### BREAKING | ||||
| 
 | ||||
| - Make headscale fetch DERP map from URL and file [#196](https://github.com/juanfont/headscale/pull/196) | ||||
|  | ||||
							
								
								
									
										46
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								acls.go
									
									
									
									
									
								
							| @ -160,7 +160,7 @@ func (h *Headscale) generateACLPolicySrcIP( | ||||
| 	aclPolicy ACLPolicy, | ||||
| 	u string, | ||||
| ) ([]string, error) { | ||||
| 	return expandAlias(machines, aclPolicy, u) | ||||
| 	return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain) | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) generateACLPolicyDestPorts( | ||||
| @ -186,7 +186,12 @@ func (h *Headscale) generateACLPolicyDestPorts( | ||||
| 		alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1]) | ||||
| 	} | ||||
| 
 | ||||
| 	expanded, err := expandAlias(machines, aclPolicy, alias) | ||||
| 	expanded, err := expandAlias( | ||||
| 		machines, | ||||
| 		aclPolicy, | ||||
| 		alias, | ||||
| 		h.cfg.OIDC.StripEmaildomain, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -218,6 +223,7 @@ func expandAlias( | ||||
| 	machines []Machine, | ||||
| 	aclPolicy ACLPolicy, | ||||
| 	alias string, | ||||
| 	stripEmailDomain bool, | ||||
| ) ([]string, error) { | ||||
| 	ips := []string{} | ||||
| 	if alias == "*" { | ||||
| @ -225,7 +231,7 @@ func expandAlias( | ||||
| 	} | ||||
| 
 | ||||
| 	if strings.HasPrefix(alias, "group:") { | ||||
| 		namespaces, err := expandGroup(aclPolicy, alias) | ||||
| 		namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain) | ||||
| 		if err != nil { | ||||
| 			return ips, err | ||||
| 		} | ||||
| @ -240,7 +246,7 @@ func expandAlias( | ||||
| 	} | ||||
| 
 | ||||
| 	if strings.HasPrefix(alias, "tag:") { | ||||
| 		owners, err := expandTagOwners(aclPolicy, alias) | ||||
| 		owners, err := expandTagOwners(aclPolicy, alias, stripEmailDomain) | ||||
| 		if err != nil { | ||||
| 			return ips, err | ||||
| 		} | ||||
| @ -383,7 +389,11 @@ func filterMachinesByNamespace(machines []Machine, namespace string) []Machine { | ||||
| 
 | ||||
| // expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
 | ||||
| // a group cannot be composed of groups.
 | ||||
| func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) { | ||||
| func expandTagOwners( | ||||
| 	aclPolicy ACLPolicy, | ||||
| 	tag string, | ||||
| 	stripEmailDomain bool, | ||||
| ) ([]string, error) { | ||||
| 	var owners []string | ||||
| 	ows, ok := aclPolicy.TagOwners[tag] | ||||
| 	if !ok { | ||||
| @ -395,7 +405,7 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) { | ||||
| 	} | ||||
| 	for _, owner := range ows { | ||||
| 		if strings.HasPrefix(owner, "group:") { | ||||
| 			gs, err := expandGroup(aclPolicy, owner) | ||||
| 			gs, err := expandGroup(aclPolicy, owner, stripEmailDomain) | ||||
| 			if err != nil { | ||||
| 				return []string{}, err | ||||
| 			} | ||||
| @ -410,8 +420,13 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) { | ||||
| 
 | ||||
| // expandGroup will return the list of namespace inside the group
 | ||||
| // after some validation.
 | ||||
| func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) { | ||||
| 	groups, ok := aclPolicy.Groups[group] | ||||
| func expandGroup( | ||||
| 	aclPolicy ACLPolicy, | ||||
| 	group string, | ||||
| 	stripEmailDomain bool, | ||||
| ) ([]string, error) { | ||||
| 	outGroups := []string{} | ||||
| 	aclGroups, ok := aclPolicy.Groups[group] | ||||
| 	if !ok { | ||||
| 		return []string{}, fmt.Errorf( | ||||
| 			"group %v isn't registered. %w", | ||||
| @ -419,14 +434,23 @@ func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) { | ||||
| 			errInvalidGroup, | ||||
| 		) | ||||
| 	} | ||||
| 	for _, g := range groups { | ||||
| 		if strings.HasPrefix(g, "group:") { | ||||
| 	for _, group := range aclGroups { | ||||
| 		if strings.HasPrefix(group, "group:") { | ||||
| 			return []string{}, fmt.Errorf( | ||||
| 				"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups", | ||||
| 				errInvalidGroup, | ||||
| 			) | ||||
| 		} | ||||
| 		grp, err := NormalizeNamespaceName(group, stripEmailDomain) | ||||
| 		if err != nil { | ||||
| 			return []string{}, fmt.Errorf( | ||||
| 				"failed to normalize group %q, err: %w", | ||||
| 				group, | ||||
| 				errInvalidGroup, | ||||
| 			) | ||||
| 		} | ||||
| 		outGroups = append(outGroups, grp) | ||||
| 	} | ||||
| 
 | ||||
| 	return groups, nil | ||||
| 	return outGroups, nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										122
									
								
								acls_test.go
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								acls_test.go
									
									
									
									
									
								
							| @ -121,7 +121,6 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) { | ||||
| 		Name:           "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -168,7 +167,6 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) { | ||||
| 		Name:           "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -215,7 +213,6 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) { | ||||
| 		Name:           "testmachine", | ||||
| 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -261,7 +258,6 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { | ||||
| 		Name:           "webserver", | ||||
| 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -281,7 +277,6 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) { | ||||
| 		Name:           "user", | ||||
| 		IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo2), | ||||
| @ -375,7 +370,6 @@ func (s *Suite) TestPortNamespace(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| @ -418,7 +412,6 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    ips, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| @ -444,8 +437,9 @@ func (s *Suite) TestPortGroup(c *check.C) { | ||||
| 
 | ||||
| func Test_expandGroup(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		aclPolicy ACLPolicy | ||||
| 		group     string | ||||
| 		aclPolicy        ACLPolicy | ||||
| 		group            string | ||||
| 		stripEmailDomain bool | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| @ -462,7 +456,8 @@ func Test_expandGroup(t *testing.T) { | ||||
| 						"group:foo":  []string{"user2", "user3"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				group: "group:test", | ||||
| 				group:            "group:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"user1", "user2", "user3"}, | ||||
| 			wantErr: false, | ||||
| @ -476,15 +471,54 @@ func Test_expandGroup(t *testing.T) { | ||||
| 						"group:foo":  []string{"user2", "user3"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				group: "group:undefined", | ||||
| 				group:            "group:undefined", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{}, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Expand emails in group", | ||||
| 			args: args{ | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					Groups: Groups{ | ||||
| 						"group:admin": []string{ | ||||
| 							"joe.bar@gmail.com", | ||||
| 							"john.doe@yahoo.fr", | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				group:            "group:admin", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"joe.bar", "john.doe"}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Expand emails in group", | ||||
| 			args: args{ | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					Groups: Groups{ | ||||
| 						"group:admin": []string{ | ||||
| 							"joe.bar@gmail.com", | ||||
| 							"john.doe@yahoo.fr", | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				group:            "group:admin", | ||||
| 				stripEmailDomain: false, | ||||
| 			}, | ||||
| 			want:    []string{"joe.bar.gmail.com", "john.doe.yahoo.fr"}, | ||||
| 			wantErr: false, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, test := range tests { | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			got, err := expandGroup(test.args.aclPolicy, test.args.group) | ||||
| 			got, err := expandGroup( | ||||
| 				test.args.aclPolicy, | ||||
| 				test.args.group, | ||||
| 				test.args.stripEmailDomain, | ||||
| 			) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr) | ||||
| 
 | ||||
| @ -499,8 +533,9 @@ func Test_expandGroup(t *testing.T) { | ||||
| 
 | ||||
| func Test_expandTagOwners(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		aclPolicy ACLPolicy | ||||
| 		tag       string | ||||
| 		aclPolicy        ACLPolicy | ||||
| 		tag              string | ||||
| 		stripEmailDomain bool | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| @ -514,7 +549,8 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					TagOwners: TagOwners{"tag:test": []string{"user1"}}, | ||||
| 				}, | ||||
| 				tag: "tag:test", | ||||
| 				tag:              "tag:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"user1"}, | ||||
| 			wantErr: false, | ||||
| @ -526,7 +562,8 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 					Groups:    Groups{"group:foo": []string{"user1", "user2"}}, | ||||
| 					TagOwners: TagOwners{"tag:test": []string{"group:foo"}}, | ||||
| 				}, | ||||
| 				tag: "tag:test", | ||||
| 				tag:              "tag:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"user1", "user2"}, | ||||
| 			wantErr: false, | ||||
| @ -538,7 +575,8 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 					Groups:    Groups{"group:foo": []string{"user1", "user2"}}, | ||||
| 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}}, | ||||
| 				}, | ||||
| 				tag: "tag:test", | ||||
| 				tag:              "tag:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"user1", "user2", "user3"}, | ||||
| 			wantErr: false, | ||||
| @ -549,7 +587,8 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}}, | ||||
| 				}, | ||||
| 				tag: "tag:test", | ||||
| 				tag:              "tag:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{}, | ||||
| 			wantErr: true, | ||||
| @ -561,7 +600,8 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 					Groups:    Groups{"group:bar": []string{"user1", "user2"}}, | ||||
| 					TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}}, | ||||
| 				}, | ||||
| 				tag: "tag:test", | ||||
| 				tag:              "tag:test", | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{}, | ||||
| 			wantErr: true, | ||||
| @ -569,7 +609,11 @@ func Test_expandTagOwners(t *testing.T) { | ||||
| 	} | ||||
| 	for _, test := range tests { | ||||
| 		t.Run(test.name, func(t *testing.T) { | ||||
| 			got, err := expandTagOwners(test.args.aclPolicy, test.args.tag) | ||||
| 			got, err := expandTagOwners( | ||||
| 				test.args.aclPolicy, | ||||
| 				test.args.tag, | ||||
| 				test.args.stripEmailDomain, | ||||
| 			) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr) | ||||
| 
 | ||||
| @ -731,9 +775,10 @@ func Test_listMachinesInNamespace(t *testing.T) { | ||||
| // nolint
 | ||||
| func Test_expandAlias(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		machines  []Machine | ||||
| 		aclPolicy ACLPolicy | ||||
| 		alias     string | ||||
| 		machines         []Machine | ||||
| 		aclPolicy        ACLPolicy | ||||
| 		alias            string | ||||
| 		stripEmailDomain bool | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name    string | ||||
| @ -753,7 +798,8 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				aclPolicy: ACLPolicy{}, | ||||
| 				aclPolicy:        ACLPolicy{}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"*"}, | ||||
| 			wantErr: false, | ||||
| @ -791,6 +837,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					Groups: Groups{"group:accountant": []string{"joe", "marc"}}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"}, | ||||
| 			wantErr: false, | ||||
| @ -828,6 +875,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					Groups: Groups{"group:accountant": []string{"joe", "marc"}}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{}, | ||||
| 			wantErr: true, | ||||
| @ -835,9 +883,10 @@ func Test_expandAlias(t *testing.T) { | ||||
| 		{ | ||||
| 			name: "simple ipaddress", | ||||
| 			args: args{ | ||||
| 				alias:     "10.0.0.3", | ||||
| 				machines:  []Machine{}, | ||||
| 				aclPolicy: ACLPolicy{}, | ||||
| 				alias:            "10.0.0.3", | ||||
| 				machines:         []Machine{}, | ||||
| 				aclPolicy:        ACLPolicy{}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"10.0.0.3"}, | ||||
| 			wantErr: false, | ||||
| @ -852,6 +901,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"), | ||||
| 					}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"192.168.1.0/24"}, | ||||
| 			wantErr: false, | ||||
| @ -859,9 +909,10 @@ func Test_expandAlias(t *testing.T) { | ||||
| 		{ | ||||
| 			name: "simple host", | ||||
| 			args: args{ | ||||
| 				alias:     "10.0.0.1", | ||||
| 				machines:  []Machine{}, | ||||
| 				aclPolicy: ACLPolicy{}, | ||||
| 				alias:            "10.0.0.1", | ||||
| 				machines:         []Machine{}, | ||||
| 				aclPolicy:        ACLPolicy{}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"10.0.0.1"}, | ||||
| 			wantErr: false, | ||||
| @ -869,9 +920,10 @@ func Test_expandAlias(t *testing.T) { | ||||
| 		{ | ||||
| 			name: "simple CIDR", | ||||
| 			args: args{ | ||||
| 				alias:     "10.0.0.0/16", | ||||
| 				machines:  []Machine{}, | ||||
| 				aclPolicy: ACLPolicy{}, | ||||
| 				alias:            "10.0.0.0/16", | ||||
| 				machines:         []Machine{}, | ||||
| 				aclPolicy:        ACLPolicy{}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"10.0.0.0/16"}, | ||||
| 			wantErr: false, | ||||
| @ -919,6 +971,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"100.64.0.1", "100.64.0.2"}, | ||||
| 			wantErr: false, | ||||
| @ -959,6 +1012,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 						"tag:accountant-webserver": []string{"group:accountant"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{}, | ||||
| 			wantErr: true, | ||||
| @ -1006,6 +1060,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 				aclPolicy: ACLPolicy{ | ||||
| 					TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}}, | ||||
| 				}, | ||||
| 				stripEmailDomain: true, | ||||
| 			}, | ||||
| 			want:    []string{"100.64.0.4"}, | ||||
| 			wantErr: false, | ||||
| @ -1017,6 +1072,7 @@ func Test_expandAlias(t *testing.T) { | ||||
| 				test.args.machines, | ||||
| 				test.args.aclPolicy, | ||||
| 				test.args.alias, | ||||
| 				test.args.stripEmailDomain, | ||||
| 			) | ||||
| 			if (err != nil) != test.wantErr { | ||||
| 				t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr) | ||||
|  | ||||
							
								
								
									
										189
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										189
									
								
								api.go
									
									
									
									
									
								
							| @ -22,7 +22,7 @@ import ( | ||||
| 
 | ||||
| const ( | ||||
| 	reservedResponseHeaderSize               = 4 | ||||
| 	RegisterMethodAuthKey                    = "authKey" | ||||
| 	RegisterMethodAuthKey                    = "authkey" | ||||
| 	RegisterMethodOIDC                       = "oidc" | ||||
| 	RegisterMethodCLI                        = "cli" | ||||
| 	ErrRegisterMethodCLIDoesNotSupportExpire = Error( | ||||
| @ -125,25 +125,50 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { | ||||
| 	machine, err := h.GetMachineByMachineKey(machineKey) | ||||
| 	if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 		log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine") | ||||
| 		newMachine := Machine{ | ||||
| 			Expiry:     &time.Time{}, | ||||
| 			MachineKey: MachinePublicKeyStripPrefix(machineKey), | ||||
| 			Name:       req.Hostinfo.Hostname, | ||||
| 		} | ||||
| 		if err := h.db.Create(&newMachine).Error; err != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Err(err). | ||||
| 				Msg("Could not create row") | ||||
| 			machineRegistrations.WithLabelValues("unknown", "web", "error", machine.Namespace.Name). | ||||
| 				Inc() | ||||
| 
 | ||||
| 		machineKeyStr := MachinePublicKeyStripPrefix(machineKey) | ||||
| 
 | ||||
| 		// If the machine has AuthKey set, handle registration via PreAuthKeys
 | ||||
| 		if req.Auth.AuthKey != "" { | ||||
| 			h.handleAuthKey(ctx, machineKey, req) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 		machine = &newMachine | ||||
| 
 | ||||
| 		// The machine did not have a key to authenticate, which means
 | ||||
| 		// that we rely on a method that calls back some how (OpenID or CLI)
 | ||||
| 		// We create the machine and then keep it around until a callback
 | ||||
| 		// happens
 | ||||
| 		newMachine := Machine{ | ||||
| 			MachineKey: machineKeyStr, | ||||
| 			Name:       req.Hostinfo.Hostname, | ||||
| 			NodeKey:    NodePublicKeyStripPrefix(req.NodeKey), | ||||
| 			LastSeen:   &now, | ||||
| 			Expiry:     &time.Time{}, | ||||
| 		} | ||||
| 
 | ||||
| 		if !req.Expiry.IsZero() { | ||||
| 			log.Trace(). | ||||
| 				Caller(). | ||||
| 				Str("machine", req.Hostinfo.Hostname). | ||||
| 				Time("expiry", req.Expiry). | ||||
| 				Msg("Non-zero expiry time requested") | ||||
| 			newMachine.Expiry = &req.Expiry | ||||
| 		} | ||||
| 
 | ||||
| 		h.registrationCache.Set( | ||||
| 			machineKeyStr, | ||||
| 			newMachine, | ||||
| 			registerCacheExpiration, | ||||
| 		) | ||||
| 
 | ||||
| 		h.handleMachineRegistrationNew(ctx, machineKey, req) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if machine.Registered { | ||||
| 	// The machine is already registered, so we need to pass through reauth or key update.
 | ||||
| 	if machine != nil { | ||||
| 		// If the NodeKey stored in headscale is the same as the key presented in a registration
 | ||||
| 		// request, then we have a node that is either:
 | ||||
| 		// - Trying to log out (sending a expiry in the past)
 | ||||
| @ -180,15 +205,6 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) { | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// If the machine has AuthKey set, handle registration via PreAuthKeys
 | ||||
| 	if req.Auth.AuthKey != "" { | ||||
| 		h.handleAuthKey(ctx, machineKey, req, *machine) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	h.handleMachineRegistrationNew(ctx, machineKey, req, *machine) | ||||
| } | ||||
| 
 | ||||
| func (h *Headscale) getMapResponse( | ||||
| @ -402,7 +418,7 @@ func (h *Headscale) handleMachineExpired( | ||||
| 		Msg("Machine registration has expired. Sending a authurl to register") | ||||
| 
 | ||||
| 	if registerRequest.Auth.AuthKey != "" { | ||||
| 		h.handleAuthKey(ctx, machineKey, registerRequest, machine) | ||||
| 		h.handleAuthKey(ctx, machineKey, registerRequest) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| @ -465,13 +481,12 @@ func (h *Headscale) handleMachineRegistrationNew( | ||||
| 	ctx *gin.Context, | ||||
| 	machineKey key.MachinePublic, | ||||
| 	registerRequest tailcfg.RegisterRequest, | ||||
| 	machine Machine, | ||||
| ) { | ||||
| 	resp := tailcfg.RegisterResponse{} | ||||
| 
 | ||||
| 	// The machine registration is new, redirect the client to the registration URL
 | ||||
| 	log.Debug(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 		Msg("The node is sending us a new NodeKey, sending auth url") | ||||
| 	if h.cfg.OIDC.Issuer != "" { | ||||
| 		resp.AuthURL = fmt.Sprintf( | ||||
| @ -484,24 +499,6 @@ func (h *Headscale) handleMachineRegistrationNew( | ||||
| 			strings.TrimSuffix(h.cfg.ServerURL, "/"), MachinePublicKeyStripPrefix(machineKey)) | ||||
| 	} | ||||
| 
 | ||||
| 	if !registerRequest.Expiry.IsZero() { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Time("expiry", registerRequest.Expiry). | ||||
| 			Msg("Non-zero expiry time requested, adding to cache") | ||||
| 		h.requestedExpiryCache.Set( | ||||
| 			machineKey.String(), | ||||
| 			registerRequest.Expiry, | ||||
| 			requestedExpiryCacheExpiration, | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey) | ||||
| 
 | ||||
| 	// save the NodeKey
 | ||||
| 	h.db.Save(&machine) | ||||
| 
 | ||||
| 	respBody, err := encode(resp, &machineKey, h.privateKey) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| @ -520,19 +517,21 @@ func (h *Headscale) handleAuthKey( | ||||
| 	ctx *gin.Context, | ||||
| 	machineKey key.MachinePublic, | ||||
| 	registerRequest tailcfg.RegisterRequest, | ||||
| 	machine Machine, | ||||
| ) { | ||||
| 	machineKeyStr := MachinePublicKeyStripPrefix(machineKey) | ||||
| 
 | ||||
| 	log.Debug(). | ||||
| 		Str("func", "handleAuthKey"). | ||||
| 		Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 		Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname) | ||||
| 	resp := tailcfg.RegisterResponse{} | ||||
| 
 | ||||
| 	pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 			Err(err). | ||||
| 			Msg("Failed authentication via AuthKey") | ||||
| 		resp.MachineAuthorized = false | ||||
| @ -541,76 +540,66 @@ func (h *Headscale) handleAuthKey( | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Str("func", "handleAuthKey"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 				Err(err). | ||||
| 				Msg("Cannot encode message") | ||||
| 			ctx.String(http.StatusInternalServerError, "") | ||||
| 			machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name). | ||||
| 			machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 				Inc() | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody) | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 			Msg("Failed authentication via AuthKey") | ||||
| 		machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			Inc() | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if machine.isRegistered() { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("machine already registered, reauthenticating") | ||||
| 	log.Debug(). | ||||
| 		Str("func", "handleAuthKey"). | ||||
| 		Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 		Msg("Authentication key was valid, proceeding to acquire IP addresses") | ||||
| 
 | ||||
| 		h.RefreshMachine(&machine, registerRequest.Expiry) | ||||
| 	} else { | ||||
| 		log.Debug(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("Authentication key was valid, proceeding to acquire IP addresses") | ||||
| 	nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey) | ||||
| 	now := time.Now().UTC() | ||||
| 
 | ||||
| 		h.ipAllocationMutex.Lock() | ||||
| 
 | ||||
| 		ips, err := h.getAvailableIPs() | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Str("func", "handleAuthKey"). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Msg("Failed to find an available IP address") | ||||
| 			machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name). | ||||
| 				Inc() | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 		log.Info(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("ips", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 			Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name) | ||||
| 
 | ||||
| 		machine.Expiry = ®isterRequest.Expiry | ||||
| 		machine.AuthKeyID = uint(pak.ID) | ||||
| 		machine.IPAddresses = ips | ||||
| 		machine.NamespaceID = pak.NamespaceID | ||||
| 
 | ||||
| 		machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey) | ||||
| 		// we update it just in case
 | ||||
| 		machine.Registered = true | ||||
| 		machine.RegisterMethod = RegisterMethodAuthKey | ||||
| 		h.db.Save(&machine) | ||||
| 
 | ||||
| 		h.ipAllocationMutex.Unlock() | ||||
| 	machineToRegister := Machine{ | ||||
| 		Name:           registerRequest.Hostinfo.Hostname, | ||||
| 		NamespaceID:    pak.Namespace.ID, | ||||
| 		MachineKey:     machineKeyStr, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		Expiry:         ®isterRequest.Expiry, | ||||
| 		NodeKey:        nodeKey, | ||||
| 		LastSeen:       &now, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| 
 | ||||
| 	pak.Used = true | ||||
| 	h.db.Save(&pak) | ||||
| 	machine, err := h.RegisterMachine( | ||||
| 		machineToRegister, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Msg("could not register machine") | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			Inc() | ||||
| 		ctx.String( | ||||
| 			http.StatusInternalServerError, | ||||
| 			"could not register machine", | ||||
| 		) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	h.UsePreAuthKey(pak) | ||||
| 
 | ||||
| 	resp.MachineAuthorized = true | ||||
| 	resp.User = *pak.Namespace.toUser() | ||||
| @ -619,21 +608,21 @@ func (h *Headscale) handleAuthKey( | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Str("func", "handleAuthKey"). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 			Err(err). | ||||
| 			Msg("Cannot encode message") | ||||
| 		machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name). | ||||
| 		machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name). | ||||
| 			Inc() | ||||
| 		ctx.String(http.StatusInternalServerError, "Extremely sad!") | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 	machineRegistrations.WithLabelValues("new", "authkey", "success", machine.Namespace.Name). | ||||
| 	machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name). | ||||
| 		Inc() | ||||
| 	ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody) | ||||
| 	log.Info(). | ||||
| 		Str("func", "handleAuthKey"). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("machine", registerRequest.Hostinfo.Hostname). | ||||
| 		Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")). | ||||
| 		Msg("Successfully authenticated via AuthKey") | ||||
| } | ||||
|  | ||||
							
								
								
									
										29
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								app.go
									
									
									
									
									
								
							| @ -55,8 +55,8 @@ const ( | ||||
| 	HTTPReadTimeout    = 30 * time.Second | ||||
| 	privateKeyFileMode = 0o600 | ||||
| 
 | ||||
| 	requestedExpiryCacheExpiration      = time.Minute * 5 | ||||
| 	requestedExpiryCacheCleanupInterval = time.Minute * 10 | ||||
| 	registerCacheExpiration = time.Minute * 15 | ||||
| 	registerCacheCleanup    = time.Minute * 20 | ||||
| 
 | ||||
| 	errUnsupportedDatabase                 = Error("unsupported DB") | ||||
| 	errUnsupportedLetsEncryptChallengeType = Error( | ||||
| @ -148,11 +148,10 @@ type Headscale struct { | ||||
| 
 | ||||
| 	lastStateChange sync.Map | ||||
| 
 | ||||
| 	oidcProvider   *oidc.Provider | ||||
| 	oauth2Config   *oauth2.Config | ||||
| 	oidcStateCache *cache.Cache | ||||
| 	oidcProvider *oidc.Provider | ||||
| 	oauth2Config *oauth2.Config | ||||
| 
 | ||||
| 	requestedExpiryCache *cache.Cache | ||||
| 	registrationCache *cache.Cache | ||||
| 
 | ||||
| 	ipAllocationMutex sync.Mutex | ||||
| } | ||||
| @ -202,18 +201,18 @@ func NewHeadscale(cfg Config) (*Headscale, error) { | ||||
| 		return nil, errUnsupportedDatabase | ||||
| 	} | ||||
| 
 | ||||
| 	requestedExpiryCache := cache.New( | ||||
| 		requestedExpiryCacheExpiration, | ||||
| 		requestedExpiryCacheCleanupInterval, | ||||
| 	registrationCache := cache.New( | ||||
| 		registerCacheExpiration, | ||||
| 		registerCacheCleanup, | ||||
| 	) | ||||
| 
 | ||||
| 	app := Headscale{ | ||||
| 		cfg:                  cfg, | ||||
| 		dbType:               cfg.DBtype, | ||||
| 		dbString:             dbString, | ||||
| 		privateKey:           privKey, | ||||
| 		aclRules:             tailcfg.FilterAllowAll, // default allowall
 | ||||
| 		requestedExpiryCache: requestedExpiryCache, | ||||
| 		cfg:               cfg, | ||||
| 		dbType:            cfg.DBtype, | ||||
| 		dbString:          dbString, | ||||
| 		privateKey:        privKey, | ||||
| 		aclRules:          tailcfg.FilterAllowAll, // default allowall
 | ||||
| 		registrationCache: registrationCache, | ||||
| 	} | ||||
| 
 | ||||
| 	err = app.initDB() | ||||
|  | ||||
| @ -5,7 +5,6 @@ import ( | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/patrickmn/go-cache" | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| @ -50,10 +49,6 @@ func (s *Suite) ResetDB(c *check.C) { | ||||
| 		cfg:      cfg, | ||||
| 		dbType:   "sqlite3", | ||||
| 		dbString: tmpDir + "/headscale_test.db", | ||||
| 		requestedExpiryCache: cache.New( | ||||
| 			requestedExpiryCacheExpiration, | ||||
| 			requestedExpiryCacheCleanupInterval, | ||||
| 		), | ||||
| 	} | ||||
| 	err = app.initDB() | ||||
| 	if err != nil { | ||||
|  | ||||
							
								
								
									
										38
									
								
								cli_test.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								cli_test.go
									
									
									
									
									
								
							| @ -1,38 +0,0 @@ | ||||
| package headscale | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"gopkg.in/check.v1" | ||||
| 	"inet.af/netaddr" | ||||
| ) | ||||
| 
 | ||||
| func (s *Suite) TestRegisterMachine(c *check.C) { | ||||
| 	namespace, err := app.CreateNamespace("test") | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	now := time.Now().UTC() | ||||
| 
 | ||||
| 	machine := Machine{ | ||||
| 		ID:          0, | ||||
| 		MachineKey:  "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e", | ||||
| 		NodeKey:     "bar", | ||||
| 		DiscoKey:    "faa", | ||||
| 		Name:        "testmachine", | ||||
| 		NamespaceID: namespace.ID, | ||||
| 		IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")}, | ||||
| 		Expiry:      &now, | ||||
| 	} | ||||
| 	err = app.db.Save(&machine).Error | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	_, err = app.GetMachine(namespace.Name, machine.Name) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 
 | ||||
| 	machineAfterRegistering, err := app.RegisterMachine( | ||||
| 		machine.MachineKey, | ||||
| 		namespace.Name, | ||||
| 	) | ||||
| 	c.Assert(err, check.IsNil) | ||||
| 	c.Assert(machineAfterRegistering.Registered, check.Equals, true) | ||||
| } | ||||
							
								
								
									
										33
									
								
								db.go
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								db.go
									
									
									
									
									
								
							| @ -8,6 +8,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/glebarez/sqlite" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"gorm.io/driver/postgres" | ||||
| 	"gorm.io/gorm" | ||||
| 	"gorm.io/gorm/logger" | ||||
| @ -39,6 +40,38 @@ func (h *Headscale) initDB() error { | ||||
| 
 | ||||
| 	_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses") | ||||
| 
 | ||||
| 	// If the Machine table has a column for registered,
 | ||||
| 	// find all occourences of "false" and drop them. Then
 | ||||
| 	// remove the column.
 | ||||
| 	if db.Migrator().HasColumn(&Machine{}, "registered") { | ||||
| 		log.Info(). | ||||
| 			Msg(`Database has legacy "registered" column in machine, removing...`) | ||||
| 
 | ||||
| 		machines := Machines{} | ||||
| 		if err := h.db.Not("registered").Find(&machines).Error; err != nil { | ||||
| 			log.Error().Err(err).Msg("Error accessing db") | ||||
| 		} | ||||
| 
 | ||||
| 		for _, machine := range machines { | ||||
| 			log.Info(). | ||||
| 				Str("machine", machine.Name). | ||||
| 				Str("machine_key", machine.MachineKey). | ||||
| 				Msg("Deleting unregistered machine") | ||||
| 			if err := h.db.Delete(&Machine{}, machine.ID).Error; err != nil { | ||||
| 				log.Error(). | ||||
| 					Err(err). | ||||
| 					Str("machine", machine.Name). | ||||
| 					Str("machine_key", machine.MachineKey). | ||||
| 					Msg("Error deleting unregistered machine") | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		err := db.Migrator().DropColumn(&Machine{}, "registered") | ||||
| 		if err != nil { | ||||
| 			log.Error().Err(err).Msg("Error dropping registered column") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = db.AutoMigrate(&Machine{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  | ||||
| @ -164,7 +164,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| @ -182,7 +181,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| @ -200,7 +198,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| @ -218,7 +215,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(PreAuthKey2InShared1.ID), | ||||
| @ -311,7 +307,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared1.ID), | ||||
| @ -329,7 +324,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared2.ID), | ||||
| @ -347,7 +341,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyInShared3.ID), | ||||
| @ -365,7 +358,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2InShared1.ID), | ||||
|  | ||||
| @ -85,13 +85,12 @@ type Machine struct { | ||||
| 	IpAddresses          []string               `protobuf:"bytes,5,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` | ||||
| 	Name                 string                 `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` | ||||
| 	Namespace            *Namespace             `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,omitempty"` | ||||
| 	Registered           bool                   `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"` | ||||
| 	RegisterMethod       RegisterMethod         `protobuf:"varint,9,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"` | ||||
| 	LastSeen             *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` | ||||
| 	LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"` | ||||
| 	Expiry               *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expiry,proto3" json:"expiry,omitempty"` | ||||
| 	PreAuthKey           *PreAuthKey            `protobuf:"bytes,13,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"` | ||||
| 	CreatedAt            *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | ||||
| 	LastSeen             *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` | ||||
| 	LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"` | ||||
| 	Expiry               *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expiry,proto3" json:"expiry,omitempty"` | ||||
| 	PreAuthKey           *PreAuthKey            `protobuf:"bytes,11,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"` | ||||
| 	CreatedAt            *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` | ||||
| 	RegisterMethod       RegisterMethod         `protobuf:"varint,13,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) Reset() { | ||||
| @ -175,20 +174,6 @@ func (x *Machine) GetNamespace() *Namespace { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetRegistered() bool { | ||||
| 	if x != nil { | ||||
| 		return x.Registered | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetRegisterMethod() RegisterMethod { | ||||
| 	if x != nil { | ||||
| 		return x.RegisterMethod | ||||
| 	} | ||||
| 	return RegisterMethod_REGISTER_METHOD_UNSPECIFIED | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetLastSeen() *timestamppb.Timestamp { | ||||
| 	if x != nil { | ||||
| 		return x.LastSeen | ||||
| @ -224,6 +209,13 @@ func (x *Machine) GetCreatedAt() *timestamppb.Timestamp { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (x *Machine) GetRegisterMethod() RegisterMethod { | ||||
| 	if x != nil { | ||||
| 		return x.RegisterMethod | ||||
| 	} | ||||
| 	return RegisterMethod_REGISTER_METHOD_UNSPECIFIED | ||||
| } | ||||
| 
 | ||||
| type RegisterMachineRequest struct { | ||||
| 	state         protoimpl.MessageState | ||||
| 	sizeCache     protoimpl.SizeCache | ||||
| @ -822,7 +814,7 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{ | ||||
| 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, | ||||
| 	0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73, | ||||
| 	0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, | ||||
| 	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63, | ||||
| 	0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdd, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63, | ||||
| 	0x68, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, | ||||
| 	0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, | ||||
| 	0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, | ||||
| @ -836,33 +828,31 @@ var file_headscale_v1_machine_proto_rawDesc = []byte{ | ||||
| 	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, | ||||
| 	0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, | ||||
| 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, | ||||
| 	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, | ||||
| 	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, | ||||
| 	0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, | ||||
| 	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, | ||||
| 	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, | ||||
| 	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, | ||||
| 	0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, | ||||
| 	0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, | ||||
| 	0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, | ||||
| 	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, | ||||
| 	0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, | ||||
| 	0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, | ||||
| 	0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, | ||||
| 	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, | ||||
| 	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, | ||||
| 	0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, | ||||
| 	0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, | ||||
| 	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, | ||||
| 	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, | ||||
| 	0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, | ||||
| 	0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, | ||||
| 	0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, | ||||
| 	0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, | ||||
| 	0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, | ||||
| 	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, | ||||
| 	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, | ||||
| 	0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, | ||||
| 	0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x6c, | ||||
| 	0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, | ||||
| 	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, | ||||
| 	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, | ||||
| 	0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, | ||||
| 	0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x09, | ||||
| 	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, | ||||
| 	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, | ||||
| 	0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, | ||||
| 	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, | ||||
| 	0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, | ||||
| 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, | ||||
| 	0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, | ||||
| 	0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, | ||||
| 	0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, | ||||
| 	0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, | ||||
| 	0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, | ||||
| 	0x64, 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, | ||||
| 	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, | ||||
| 	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, | ||||
| 	0x74, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, | ||||
| 	0x74, 0x68, 0x6f, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, | ||||
| 	0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, | ||||
| 	0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, | ||||
| 	0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, | ||||
| 	0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, | ||||
| 	0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, | ||||
| 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, | ||||
| @ -962,12 +952,12 @@ var file_headscale_v1_machine_proto_goTypes = []interface{}{ | ||||
| } | ||||
| var file_headscale_v1_machine_proto_depIdxs = []int32{ | ||||
| 	14, // 0: headscale.v1.Machine.namespace:type_name -> headscale.v1.Namespace
 | ||||
| 	0,  // 1: headscale.v1.Machine.register_method:type_name -> headscale.v1.RegisterMethod
 | ||||
| 	15, // 2: headscale.v1.Machine.last_seen:type_name -> google.protobuf.Timestamp
 | ||||
| 	15, // 3: headscale.v1.Machine.last_successful_update:type_name -> google.protobuf.Timestamp
 | ||||
| 	15, // 4: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
 | ||||
| 	16, // 5: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
 | ||||
| 	15, // 6: headscale.v1.Machine.created_at:type_name -> google.protobuf.Timestamp
 | ||||
| 	15, // 1: headscale.v1.Machine.last_seen:type_name -> google.protobuf.Timestamp
 | ||||
| 	15, // 2: headscale.v1.Machine.last_successful_update:type_name -> google.protobuf.Timestamp
 | ||||
| 	15, // 3: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
 | ||||
| 	16, // 4: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
 | ||||
| 	15, // 5: headscale.v1.Machine.created_at:type_name -> google.protobuf.Timestamp
 | ||||
| 	0,  // 6: headscale.v1.Machine.register_method:type_name -> headscale.v1.RegisterMethod
 | ||||
| 	1,  // 7: headscale.v1.RegisterMachineResponse.machine:type_name -> headscale.v1.Machine
 | ||||
| 	1,  // 8: headscale.v1.GetMachineResponse.machine:type_name -> headscale.v1.Machine
 | ||||
| 	1,  // 9: headscale.v1.ExpireMachineResponse.machine:type_name -> headscale.v1.Machine
 | ||||
|  | ||||
| @ -885,12 +885,6 @@ | ||||
|         "namespace": { | ||||
|           "$ref": "#/definitions/v1Namespace" | ||||
|         }, | ||||
|         "registered": { | ||||
|           "type": "boolean" | ||||
|         }, | ||||
|         "registerMethod": { | ||||
|           "$ref": "#/definitions/v1RegisterMethod" | ||||
|         }, | ||||
|         "lastSeen": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
| @ -909,6 +903,9 @@ | ||||
|         "createdAt": { | ||||
|           "type": "string", | ||||
|           "format": "date-time" | ||||
|         }, | ||||
|         "registerMethod": { | ||||
|           "$ref": "#/definitions/v1RegisterMethod" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
							
								
								
									
										14
									
								
								grpcv1.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								grpcv1.go
									
									
									
									
									
								
							| @ -157,9 +157,11 @@ func (api headscaleV1APIServer) RegisterMachine( | ||||
| 		Str("namespace", request.GetNamespace()). | ||||
| 		Str("machine_key", request.GetKey()). | ||||
| 		Msg("Registering machine") | ||||
| 	machine, err := api.h.RegisterMachine( | ||||
| 
 | ||||
| 	machine, err := api.h.RegisterMachineFromAuthCallback( | ||||
| 		request.GetKey(), | ||||
| 		request.GetNamespace(), | ||||
| 		RegisterMethodCLI, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @ -379,11 +381,11 @@ func (api headscaleV1APIServer) DebugCreateMachine( | ||||
| 		HostInfo: HostInfo(hostinfo), | ||||
| 	} | ||||
| 
 | ||||
| 	// log.Trace().Caller().Interface("machine", newMachine).Msg("")
 | ||||
| 
 | ||||
| 	if err := api.h.db.Create(&newMachine).Error; err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	api.h.registrationCache.Set( | ||||
| 		request.GetKey(), | ||||
| 		newMachine, | ||||
| 		registerCacheExpiration, | ||||
| 	) | ||||
| 
 | ||||
| 	return &v1.DebugCreateMachineResponse{Machine: newMachine.toProto()}, nil | ||||
| } | ||||
|  | ||||
| @ -621,12 +621,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	assert.Equal(s.T(), "machine-4", listAll[3].Name) | ||||
| 	assert.Equal(s.T(), "machine-5", listAll[4].Name) | ||||
| 
 | ||||
| 	assert.True(s.T(), listAll[0].Registered) | ||||
| 	assert.True(s.T(), listAll[1].Registered) | ||||
| 	assert.True(s.T(), listAll[2].Registered) | ||||
| 	assert.True(s.T(), listAll[3].Registered) | ||||
| 	assert.True(s.T(), listAll[4].Registered) | ||||
| 
 | ||||
| 	otherNamespaceMachineKeys := []string{ | ||||
| 		"b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e", | ||||
| 		"dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584", | ||||
| @ -710,9 +704,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 	assert.Equal(s.T(), "otherNamespace-machine-1", listAllWithotherNamespace[5].Name) | ||||
| 	assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].Name) | ||||
| 
 | ||||
| 	assert.True(s.T(), listAllWithotherNamespace[5].Registered) | ||||
| 	assert.True(s.T(), listAllWithotherNamespace[6].Registered) | ||||
| 
 | ||||
| 	// Test list all nodes after added otherNamespace
 | ||||
| 	listOnlyotherNamespaceMachineNamespaceResult, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -752,9 +743,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() { | ||||
| 		listOnlyotherNamespaceMachineNamespace[1].Name, | ||||
| 	) | ||||
| 
 | ||||
| 	assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[0].Registered) | ||||
| 	assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[1].Registered) | ||||
| 
 | ||||
| 	// Delete a machines
 | ||||
| 	_, err = ExecuteCommand( | ||||
| 		&s.headscale, | ||||
| @ -979,7 +967,6 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() { | ||||
| 
 | ||||
| 	assert.Equal(s.T(), uint64(1), machine.Id) | ||||
| 	assert.Equal(s.T(), "route-machine", machine.Name) | ||||
| 	assert.True(s.T(), machine.Registered) | ||||
| 
 | ||||
| 	listAllResult, err := ExecuteCommand( | ||||
| 		&s.headscale, | ||||
|  | ||||
							
								
								
									
										150
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								machine.go
									
									
									
									
									
								
							| @ -18,11 +18,14 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	errMachineNotFound            = Error("machine not found") | ||||
| 	errMachineAlreadyRegistered   = Error("machine already registered") | ||||
| 	errMachineRouteIsNotAvailable = Error("route is not available on machine") | ||||
| 	errMachineAddressesInvalid    = Error("failed to parse machine addresses") | ||||
| 	errHostnameTooLong            = Error("Hostname too long") | ||||
| 	errMachineNotFound                  = Error("machine not found") | ||||
| 	errMachineRouteIsNotAvailable       = Error("route is not available on machine") | ||||
| 	errMachineAddressesInvalid          = Error("failed to parse machine addresses") | ||||
| 	errMachineNotFoundRegistrationCache = Error( | ||||
| 		"machine not found in registration cache", | ||||
| 	) | ||||
| 	errCouldNotConvertMachineInterface = Error("failed to convert machine interface") | ||||
| 	errHostnameTooLong                 = Error("Hostname too long") | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -40,10 +43,11 @@ type Machine struct { | ||||
| 	NamespaceID uint | ||||
| 	Namespace   Namespace `gorm:"foreignKey:NamespaceID"` | ||||
| 
 | ||||
| 	Registered     bool // temp
 | ||||
| 	RegisterMethod string | ||||
| 	AuthKeyID      uint | ||||
| 	AuthKey        *PreAuthKey | ||||
| 
 | ||||
| 	// TODO(kradalby): This seems like irrelevant information?
 | ||||
| 	AuthKeyID uint | ||||
| 	AuthKey   *PreAuthKey | ||||
| 
 | ||||
| 	LastSeen             *time.Time | ||||
| 	LastSuccessfulUpdate *time.Time | ||||
| @ -63,11 +67,6 @@ type ( | ||||
| 	MachinesP []*Machine | ||||
| ) | ||||
| 
 | ||||
| // For the time being this method is rather naive.
 | ||||
| func (machine Machine) isRegistered() bool { | ||||
| 	return machine.Registered | ||||
| } | ||||
| 
 | ||||
| type MachineAddresses []netaddr.IP | ||||
| 
 | ||||
| func (ma MachineAddresses) ToStringSlice() []string { | ||||
| @ -114,7 +113,7 @@ func (machine Machine) isExpired() bool { | ||||
| 	// If Expiry is not set, the client has not indicated that
 | ||||
| 	// it wants an expiry time, it is therefor considered
 | ||||
| 	// to mean "not expired"
 | ||||
| 	if machine.Expiry.IsZero() { | ||||
| 	if machine.Expiry == nil || machine.Expiry.IsZero() { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -171,6 +170,12 @@ func getFilteredByACLPeers( | ||||
| 				machine.IPAddresses.ToStringSlice(), | ||||
| 				peer.IPAddresses.ToStringSlice(), | ||||
| 			) || // match source and destination
 | ||||
| 				matchSourceAndDestinationWithRule( | ||||
| 					rule.SrcIPs, | ||||
| 					dst, | ||||
| 					peer.IPAddresses.ToStringSlice(), | ||||
| 					machine.IPAddresses.ToStringSlice(), | ||||
| 				) || // match return path
 | ||||
| 				matchSourceAndDestinationWithRule( | ||||
| 					rule.SrcIPs, | ||||
| 					dst, | ||||
| @ -180,9 +185,21 @@ func getFilteredByACLPeers( | ||||
| 				matchSourceAndDestinationWithRule( | ||||
| 					rule.SrcIPs, | ||||
| 					dst, | ||||
| 					[]string{"*"}, | ||||
| 					[]string{"*"}, | ||||
| 				) || // match source and all destination
 | ||||
| 				matchSourceAndDestinationWithRule( | ||||
| 					rule.SrcIPs, | ||||
| 					dst, | ||||
| 					[]string{"*"}, | ||||
| 					peer.IPAddresses.ToStringSlice(), | ||||
| 				) || // match source and all destination
 | ||||
| 				matchSourceAndDestinationWithRule( | ||||
| 					rule.SrcIPs, | ||||
| 					dst, | ||||
| 					[]string{"*"}, | ||||
| 					machine.IPAddresses.ToStringSlice(), | ||||
| 				) { // match return path
 | ||||
| 				) { // match all sources and source
 | ||||
| 				peers[peer.ID] = peer | ||||
| 			} | ||||
| 		} | ||||
| @ -212,7 +229,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("machine_key <> ? AND registered", | ||||
| 	if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("machine_key <> ?", | ||||
| 		machine.MachineKey).Find(&machines).Error; err != nil { | ||||
| 		log.Error().Err(err).Msg("Error accessing db") | ||||
| 
 | ||||
| @ -275,7 +292,7 @@ func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	for _, peer := range peers { | ||||
| 		if peer.isRegistered() && !peer.isExpired() { | ||||
| 		if !peer.isExpired() { | ||||
| 			validPeers = append(validPeers, peer) | ||||
| 		} | ||||
| 	} | ||||
| @ -364,8 +381,6 @@ func (h *Headscale) RefreshMachine(machine *Machine, expiry time.Time) { | ||||
| 
 | ||||
| // DeleteMachine softs deletes a Machine from the database.
 | ||||
| func (h *Headscale) DeleteMachine(machine *Machine) error { | ||||
| 	machine.Registered = false | ||||
| 	h.db.Save(&machine) // we mark it as unregistered, just in case
 | ||||
| 	if err := h.db.Delete(&machine).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -581,7 +596,7 @@ func (machine Machine) toNode( | ||||
| 		LastSeen: machine.LastSeen, | ||||
| 
 | ||||
| 		KeepAlive:         true, | ||||
| 		MachineAuthorized: machine.Registered, | ||||
| 		MachineAuthorized: !machine.isExpired(), | ||||
| 		Capabilities:      []string{tailcfg.CapabilityFileSharing}, | ||||
| 	} | ||||
| 
 | ||||
| @ -599,8 +614,6 @@ func (machine *Machine) toProto() *v1.Machine { | ||||
| 		Name:        machine.Name, | ||||
| 		Namespace:   machine.Namespace.toProto(), | ||||
| 
 | ||||
| 		Registered: machine.Registered, | ||||
| 
 | ||||
| 		// TODO(kradalby): Implement register method enum converter
 | ||||
| 		// RegisterMethod: ,
 | ||||
| 
 | ||||
| @ -628,74 +641,50 @@ func (machine *Machine) toProto() *v1.Machine { | ||||
| 	return machineProto | ||||
| } | ||||
| 
 | ||||
| // RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
 | ||||
| func (h *Headscale) RegisterMachine( | ||||
| func (h *Headscale) RegisterMachineFromAuthCallback( | ||||
| 	machineKeyStr string, | ||||
| 	namespaceName string, | ||||
| 	registrationMethod string, | ||||
| ) (*Machine, error) { | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if machineInterface, ok := h.registrationCache.Get(machineKeyStr); ok { | ||||
| 		if registrationMachine, ok := machineInterface.(Machine); ok { | ||||
| 			namespace, err := h.GetNamespace(namespaceName) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf( | ||||
| 					"failed to find namespace in register machine from auth callback, %w", | ||||
| 					err, | ||||
| 				) | ||||
| 			} | ||||
| 
 | ||||
| 	var machineKey key.MachinePublic | ||||
| 	err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 			registrationMachine.NamespaceID = namespace.ID | ||||
| 			registrationMachine.RegisterMethod = registrationMethod | ||||
| 
 | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine_key_str", machineKeyStr). | ||||
| 		Str("machine_key", machineKey.String()). | ||||
| 		Msg("Registering machine") | ||||
| 			machine, err := h.RegisterMachine( | ||||
| 				registrationMachine, | ||||
| 			) | ||||
| 
 | ||||
| 	machine, err := h.GetMachineByMachineKey(machineKey) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
 | ||||
| 	// This means that if a user is to slow with register a machine, it will possibly not
 | ||||
| 	// have the correct expiry.
 | ||||
| 	requestedTime := time.Time{} | ||||
| 	if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("Expiry time found in cache, assigning to node") | ||||
| 		if reqTime, ok := requestedTimeIf.(time.Time); ok { | ||||
| 			requestedTime = reqTime | ||||
| 			return machine, err | ||||
| 		} else { | ||||
| 			return nil, errCouldNotConvertMachineInterface | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if machine.isRegistered() { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("machine already registered, reauthenticating") | ||||
| 	return nil, errMachineNotFoundRegistrationCache | ||||
| } | ||||
| 
 | ||||
| 		h.RefreshMachine(machine, requestedTime) | ||||
| 
 | ||||
| 		return machine, nil | ||||
| 	} | ||||
| // RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
 | ||||
| func (h *Headscale) RegisterMachine(machine Machine, | ||||
| ) (*Machine, error) { | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine_key", machine.MachineKey). | ||||
| 		Msg("Registering machine") | ||||
| 
 | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Msg("Attempting to register machine") | ||||
| 
 | ||||
| 	if machine.isRegistered() { | ||||
| 		err := errMachineAlreadyRegistered | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("Attempting to register machine") | ||||
| 
 | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	h.ipAllocationMutex.Lock() | ||||
| 	defer h.ipAllocationMutex.Unlock() | ||||
| 
 | ||||
| @ -710,17 +699,8 @@ func (h *Headscale) RegisterMachine( | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace(). | ||||
| 		Caller(). | ||||
| 		Str("machine", machine.Name). | ||||
| 		Str("ip", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 		Msg("Found IP for host") | ||||
| 
 | ||||
| 	machine.IPAddresses = ips | ||||
| 	machine.NamespaceID = namespace.ID | ||||
| 	machine.Registered = true | ||||
| 	machine.RegisterMethod = RegisterMethodCLI | ||||
| 	machine.Expiry = &requestedTime | ||||
| 
 | ||||
| 	h.db.Save(&machine) | ||||
| 
 | ||||
| 	log.Trace(). | ||||
| @ -729,7 +709,7 @@ func (h *Headscale) RegisterMachine( | ||||
| 		Str("ip", strings.Join(ips.ToStringSlice(), ",")). | ||||
| 		Msg("Machine registered with the database") | ||||
| 
 | ||||
| 	return machine, nil | ||||
| 	return &machine, nil | ||||
| } | ||||
| 
 | ||||
| func (machine *Machine) GetAdvertisedRoutes() []netaddr.IPPrefix { | ||||
|  | ||||
							
								
								
									
										212
									
								
								machine_test.go
									
									
									
									
									
								
							
							
						
						
									
										212
									
								
								machine_test.go
									
									
									
									
									
								
							| @ -29,7 +29,6 @@ func (s *Suite) TestGetMachine(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -56,7 +55,6 @@ func (s *Suite) TestGetMachineByID(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -76,7 +74,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(1), | ||||
| 	} | ||||
| @ -99,7 +96,6 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine3", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(1), | ||||
| 	} | ||||
| @ -130,7 +126,6 @@ func (s *Suite) TestListPeers(c *check.C) { | ||||
| 			DiscoKey:       "faa" + strconv.Itoa(index), | ||||
| 			Name:           "testmachine" + strconv.Itoa(index), | ||||
| 			NamespaceID:    namespace.ID, | ||||
| 			Registered:     true, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(pak.ID), | ||||
| 		} | ||||
| @ -179,7 +174,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) { | ||||
| 			}, | ||||
| 			Name:           "testmachine" + strconv.Itoa(index), | ||||
| 			NamespaceID:    stor[index%2].namespace.ID, | ||||
| 			Registered:     true, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(stor[index%2].key.ID), | ||||
| 		} | ||||
| @ -246,7 +240,6 @@ func (s *Suite) TestExpireMachine(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		Expiry:         &time.Time{}, | ||||
| @ -284,6 +277,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // nolint
 | ||||
| func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		machines []Machine | ||||
| @ -431,7 +425,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: &Machine{ // current machine
 | ||||
| 					ID:          1, | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 				}, | ||||
| @ -444,6 +438,208 @@ func Test_getFilteredByACLPeers(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "rules allows all hosts to reach one destination", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ // list of all machines in the database
 | ||||
| 					{ | ||||
| 						ID: 1, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | ||||
| 					{ | ||||
| 						SrcIPs: []string{"*"}, | ||||
| 						DstPorts: []tailcfg.NetPortRange{ | ||||
| 							{IP: "100.64.0.2"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: &Machine{ // current machine
 | ||||
| 					ID: 1, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID: 2, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.2"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "rules allows all hosts to reach one destination, destination can reach all hosts", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ // list of all machines in the database
 | ||||
| 					{ | ||||
| 						ID: 1, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | ||||
| 					{ | ||||
| 						SrcIPs: []string{"*"}, | ||||
| 						DstPorts: []tailcfg.NetPortRange{ | ||||
| 							{IP: "100.64.0.2"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: &Machine{ // current machine
 | ||||
| 					ID: 2, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.2"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID: 1, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID: 3, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.3"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "rule allows all hosts to reach all destinations", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ // list of all machines in the database
 | ||||
| 					{ | ||||
| 						ID: 1, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | ||||
| 					{ | ||||
| 						SrcIPs: []string{"*"}, | ||||
| 						DstPorts: []tailcfg.NetPortRange{ | ||||
| 							{IP: "*"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				machine: &Machine{ // current machine
 | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{ | ||||
| 				{ | ||||
| 					ID: 1, | ||||
| 					IPAddresses: MachineAddresses{ | ||||
| 						netaddr.MustParseIP("100.64.0.1"), | ||||
| 					}, | ||||
| 					Namespace: Namespace{Name: "joe"}, | ||||
| 				}, | ||||
| 				{ | ||||
| 					ID:          3, | ||||
| 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 					Namespace:   Namespace{Name: "mickael"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "without rule all communications are forbidden", | ||||
| 			args: args{ | ||||
| 				machines: []Machine{ // list of all machines in the database
 | ||||
| 					{ | ||||
| 						ID: 1, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.1"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "joe"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 2, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.2"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "marc"}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						ID: 3, | ||||
| 						IPAddresses: MachineAddresses{ | ||||
| 							netaddr.MustParseIP("100.64.0.3"), | ||||
| 						}, | ||||
| 						Namespace: Namespace{Name: "mickael"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | ||||
| 				}, | ||||
| 				machine: &Machine{ // current machine
 | ||||
| 					ID:          2, | ||||
| 					IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 					Namespace:   Namespace{Name: "marc"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: Machines{}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
|  | ||||
| @ -54,7 +54,6 @@ func (s *Suite) TestDestroyNamespaceErrors(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -146,7 +145,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_1", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.1")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared1.ID), | ||||
| @ -164,7 +162,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_2", | ||||
| 		NamespaceID:    namespaceShared2.ID, | ||||
| 		Namespace:      *namespaceShared2, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.2")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared2.ID), | ||||
| @ -182,7 +179,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_3", | ||||
| 		NamespaceID:    namespaceShared3.ID, | ||||
| 		Namespace:      *namespaceShared3, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.3")}, | ||||
| 		AuthKeyID:      uint(preAuthKeyShared3.ID), | ||||
| @ -200,7 +196,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) { | ||||
| 		Name:           "test_get_shared_nodes_4", | ||||
| 		NamespaceID:    namespaceShared1.ID, | ||||
| 		Namespace:      *namespaceShared1, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		IPAddresses:    []netaddr.IP{netaddr.MustParseIP("100.64.0.4")}, | ||||
| 		AuthKeyID:      uint(preAuthKey2Shared1.ID), | ||||
|  | ||||
							
								
								
									
										137
									
								
								oidc.go
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								oidc.go
									
									
									
									
									
								
							| @ -10,21 +10,16 @@ import ( | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/coreos/go-oidc/v3/oidc" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/patrickmn/go-cache" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"golang.org/x/oauth2" | ||||
| 	"gorm.io/gorm" | ||||
| 	"tailscale.com/types/key" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	oidcStateCacheExpiration      = time.Minute * 5 | ||||
| 	oidcStateCacheCleanupInterval = time.Minute * 10 | ||||
| 	randomByteSize                = 16 | ||||
| 	randomByteSize = 16 | ||||
| ) | ||||
| 
 | ||||
| type IDTokenClaims struct { | ||||
| @ -61,14 +56,6 @@ func (h *Headscale) initOIDC() error { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// init the state cache if it hasn't been already
 | ||||
| 	if h.oidcStateCache == nil { | ||||
| 		h.oidcStateCache = cache.New( | ||||
| 			oidcStateCacheExpiration, | ||||
| 			oidcStateCacheCleanupInterval, | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -101,7 +88,7 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) { | ||||
| 	stateStr := hex.EncodeToString(randomBlob)[:32] | ||||
| 
 | ||||
| 	// place the machine key into the state cache, so it can be retrieved later
 | ||||
| 	h.oidcStateCache.Set(stateStr, machineKeyStr, oidcStateCacheExpiration) | ||||
| 	h.registrationCache.Set(stateStr, machineKeyStr, registerCacheExpiration) | ||||
| 
 | ||||
| 	authURL := h.oauth2Config.AuthCodeURL(stateStr) | ||||
| 	log.Debug().Msgf("Redirecting to %s for authentication", authURL) | ||||
| @ -125,7 +112,6 @@ var oidcCallbackTemplate = template.Must( | ||||
| 	</html>`), | ||||
| ) | ||||
| 
 | ||||
| // TODO: Why is the entire machine registration logic duplicated here?
 | ||||
| // OIDCCallback handles the callback from the OIDC endpoint
 | ||||
| // Retrieves the mkey from the state cache and adds the machine to the users email namespace
 | ||||
| // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
 | ||||
| @ -197,7 +183,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 	} | ||||
| 
 | ||||
| 	// retrieve machinekey from state cache
 | ||||
| 	machineKeyIf, machineKeyFound := h.oidcStateCache.Get(state) | ||||
| 	machineKeyIf, machineKeyFound := h.registrationCache.Get(state) | ||||
| 
 | ||||
| 	if !machineKeyFound { | ||||
| 		log.Error(). | ||||
| @ -207,10 +193,12 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	machineKeyStr, machineKeyOK := machineKeyIf.(string) | ||||
| 	machineKeyFromCache, machineKeyOK := machineKeyIf.(string) | ||||
| 
 | ||||
| 	var machineKey key.MachinePublic | ||||
| 	err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr))) | ||||
| 	err = machineKey.UnmarshalText( | ||||
| 		[]byte(MachinePublicKeyEnsurePrefix(machineKeyFromCache)), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Msg("could not parse machine public key") | ||||
| @ -229,33 +217,19 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
 | ||||
| 	requestedTime := time.Time{} | ||||
| 	if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found { | ||||
| 		if reqTime, ok := requestedTimeIf.(time.Time); ok { | ||||
| 			requestedTime = reqTime | ||||
| 		} | ||||
| 	} | ||||
| 	// retrieve machine information if it exist
 | ||||
| 	// The error is not important, because if it does not
 | ||||
| 	// exist, then this is a new machine and we will move
 | ||||
| 	// on to registration.
 | ||||
| 	machine, _ := h.GetMachineByMachineKey(machineKey) | ||||
| 
 | ||||
| 	// retrieve machine information
 | ||||
| 	machine, err := h.GetMachineByMachineKey(machineKey) | ||||
| 	if err != nil { | ||||
| 		log.Error().Msg("machine key not found in database") | ||||
| 		ctx.String( | ||||
| 			http.StatusInternalServerError, | ||||
| 			"could not get machine info from database", | ||||
| 		) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if machine.isRegistered() { | ||||
| 	if machine != nil { | ||||
| 		log.Trace(). | ||||
| 			Caller(). | ||||
| 			Str("machine", machine.Name). | ||||
| 			Msg("machine already registered, reauthenticating") | ||||
| 
 | ||||
| 		h.RefreshMachine(machine, requestedTime) | ||||
| 		h.RefreshMachine(machine, *machine.Expiry) | ||||
| 
 | ||||
| 		var content bytes.Buffer | ||||
| 		if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{ | ||||
| @ -279,8 +253,6 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	now := time.Now().UTC() | ||||
| 
 | ||||
| 	namespaceName, err := NormalizeNamespaceName( | ||||
| 		claims.Email, | ||||
| 		h.cfg.OIDC.StripEmaildomain, | ||||
| @ -294,61 +266,58 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) { | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// register the machine if it's new
 | ||||
| 	if !machine.Registered { | ||||
| 		log.Debug().Msg("Registering new machine after successful callback") | ||||
| 	log.Debug().Msg("Registering new machine after successful callback") | ||||
| 
 | ||||
| 		namespace, err := h.GetNamespace(namespaceName) | ||||
| 		if errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 			namespace, err = h.CreateNamespace(namespaceName) | ||||
| 	namespace, err := h.GetNamespace(namespaceName) | ||||
| 	if errors.Is(err, errNamespaceNotFound) { | ||||
| 		namespace, err = h.CreateNamespace(namespaceName) | ||||
| 
 | ||||
| 			if err != nil { | ||||
| 				log.Error(). | ||||
| 					Err(err). | ||||
| 					Caller(). | ||||
| 					Msgf("could not create new namespace '%s'", namespaceName) | ||||
| 				ctx.String( | ||||
| 					http.StatusInternalServerError, | ||||
| 					"could not create new namespace", | ||||
| 				) | ||||
| 
 | ||||
| 				return | ||||
| 			} | ||||
| 		} else if err != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Err(err). | ||||
| 				Str("namespace", namespaceName). | ||||
| 				Msg("could not find or create namespace") | ||||
| 			ctx.String( | ||||
| 				http.StatusInternalServerError, | ||||
| 				"could not find or create namespace", | ||||
| 			) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		ips, err := h.getAvailableIPs() | ||||
| 		if err != nil { | ||||
| 			log.Error(). | ||||
| 				Caller(). | ||||
| 				Err(err). | ||||
| 				Msg("could not get an IP from the pool") | ||||
| 				Caller(). | ||||
| 				Msgf("could not create new namespace '%s'", namespaceName) | ||||
| 			ctx.String( | ||||
| 				http.StatusInternalServerError, | ||||
| 				"could not get an IP from the pool", | ||||
| 				"could not create new namespace", | ||||
| 			) | ||||
| 
 | ||||
| 			return | ||||
| 		} | ||||
| 	} else if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Str("namespace", namespaceName). | ||||
| 			Msg("could not find or create namespace") | ||||
| 		ctx.String( | ||||
| 			http.StatusInternalServerError, | ||||
| 			"could not find or create namespace", | ||||
| 		) | ||||
| 
 | ||||
| 		machine.IPAddresses = ips | ||||
| 		machine.NamespaceID = namespace.ID | ||||
| 		machine.Registered = true | ||||
| 		machine.RegisterMethod = RegisterMethodOIDC | ||||
| 		machine.LastSuccessfulUpdate = &now | ||||
| 		machine.Expiry = &requestedTime | ||||
| 		h.db.Save(&machine) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	machineKeyStr := MachinePublicKeyStripPrefix(machineKey) | ||||
| 
 | ||||
| 	_, err = h.RegisterMachineFromAuthCallback( | ||||
| 		machineKeyStr, | ||||
| 		namespace.Name, | ||||
| 		RegisterMethodOIDC, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		log.Error(). | ||||
| 			Caller(). | ||||
| 			Err(err). | ||||
| 			Msg("could not register machine") | ||||
| 		ctx.String( | ||||
| 			http.StatusInternalServerError, | ||||
| 			"could not register machine", | ||||
| 		) | ||||
| 
 | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var content bytes.Buffer | ||||
|  | ||||
| @ -113,6 +113,12 @@ func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // UsePreAuthKey marks a PreAuthKey as used.
 | ||||
| func (h *Headscale) UsePreAuthKey(k *PreAuthKey) { | ||||
| 	k.Used = true | ||||
| 	h.db.Save(k) | ||||
| } | ||||
| 
 | ||||
| // checkKeyValidity does the heavy lifting for validation of the PreAuthKey coming from a node
 | ||||
| // If returns no error and a PreAuthKey, it can be used.
 | ||||
| func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) { | ||||
|  | ||||
| @ -80,7 +80,6 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -105,7 +104,6 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
| @ -143,7 +141,6 @@ func (*Suite) TestEphemeralKey(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testest", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		LastSeen:       &now, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
|  | ||||
| @ -22,16 +22,16 @@ message Machine { | ||||
|     string          name         = 6; | ||||
|     Namespace namespace          = 7; | ||||
| 
 | ||||
|     bool           registered      = 8; | ||||
|     RegisterMethod register_method = 9; | ||||
| 
 | ||||
|     google.protobuf.Timestamp last_seen              = 10; | ||||
|     google.protobuf.Timestamp last_successful_update = 11; | ||||
|     google.protobuf.Timestamp expiry                 = 12; | ||||
|     google.protobuf.Timestamp last_seen              = 8; | ||||
|     google.protobuf.Timestamp last_successful_update = 9; | ||||
|     google.protobuf.Timestamp expiry                 = 10; | ||||
| 
 | ||||
|     PreAuthKey pre_auth_key = 13; | ||||
|     PreAuthKey pre_auth_key = 11; | ||||
| 
 | ||||
|     google.protobuf.Timestamp created_at = 14; | ||||
|     google.protobuf.Timestamp created_at = 12; | ||||
| 
 | ||||
|     RegisterMethod register_method = 13; | ||||
|     // google.protobuf.Timestamp updated_at = 14; | ||||
|     // google.protobuf.Timestamp deleted_at = 15; | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,6 @@ func (s *Suite) TestGetRoutes(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "test_get_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
| @ -82,7 +81,6 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "test_enable_route_machine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		HostInfo:       HostInfo(hostInfo), | ||||
|  | ||||
| @ -36,7 +36,6 @@ func (s *Suite) TestGetUsedIps(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 		IPAddresses:    ips, | ||||
| @ -85,7 +84,6 @@ func (s *Suite) TestGetMultiIp(c *check.C) { | ||||
| 			DiscoKey:       "faa", | ||||
| 			Name:           "testmachine", | ||||
| 			NamespaceID:    namespace.ID, | ||||
| 			Registered:     true, | ||||
| 			RegisterMethod: RegisterMethodAuthKey, | ||||
| 			AuthKeyID:      uint(pak.ID), | ||||
| 			IPAddresses:    ips, | ||||
| @ -176,7 +174,6 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) { | ||||
| 		DiscoKey:       "faa", | ||||
| 		Name:           "testmachine", | ||||
| 		NamespaceID:    namespace.ID, | ||||
| 		Registered:     true, | ||||
| 		RegisterMethod: RegisterMethodAuthKey, | ||||
| 		AuthKeyID:      uint(pak.ID), | ||||
| 	} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user