mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-29 18:33:05 +00:00
Resolve merge conflict
This commit is contained in:
commit
5b169010be
23 changed files with 681 additions and 510 deletions
|
@ -29,6 +29,7 @@ linters:
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
- dupl
|
- dupl
|
||||||
- makezero
|
- makezero
|
||||||
|
- maintidx
|
||||||
|
|
||||||
# We might want to enable this, but it might be a lot of work
|
# We might want to enable this, but it might be a lot of work
|
||||||
- cyclop
|
- cyclop
|
||||||
|
|
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -1,31 +1,37 @@
|
||||||
# CHANGELOG
|
# 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)
|
- 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).
|
- 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)
|
- 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)
|
- 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**:
|
**UPCOMING ### BREAKING
|
||||||
From the **next** version (`0.15.0`), all machines will be able to communicate regardless of
|
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
|
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
|
will become default. From version `0.15.0`, all limitation of communications must be done
|
||||||
with ACLs.
|
with ACLs.
|
||||||
|
|
||||||
This is a part of aligning `headscale`'s behaviour with Tailscale's upstream behaviour.
|
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
|
- 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
|
- 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.
|
- 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
|
- 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)
|
- 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)
|
- 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):**
|
**0.13.0 (2022-02-18):**
|
||||||
|
|
||||||
**Features**:
|
### Features
|
||||||
|
|
||||||
- Add IPv6 support to the prefix assigned to namespaces
|
- Add IPv6 support to the prefix assigned to namespaces
|
||||||
- Add API Key support
|
- 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
|
- `oidc.domain_map` option has been removed
|
||||||
- `strip_email_domain` option has been added (see [config-example.yaml](./config_example.yaml))
|
- `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)
|
- `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)
|
- 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):**
|
**0.12.4 (2022-01-29):**
|
||||||
|
|
||||||
**Changes**:
|
### Changes
|
||||||
|
|
||||||
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- Added Alpine container [#270](https://github.com/juanfont/headscale/pull/270)
|
||||||
- Minor updates in dependencies [#271](https://github.com/juanfont/headscale/pull/271)
|
- 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!
|
Happy New Year!
|
||||||
|
|
||||||
**Changes**:
|
### Changes
|
||||||
|
|
||||||
- Fix Docker release [#258](https://github.com/juanfont/headscale/pull/258)
|
- Fix Docker release [#258](https://github.com/juanfont/headscale/pull/258)
|
||||||
- Rewrite main docs [#262](https://github.com/juanfont/headscale/pull/262)
|
- Rewrite main docs [#262](https://github.com/juanfont/headscale/pull/262)
|
||||||
- Improve Docker docs [#263](https://github.com/juanfont/headscale/pull/263)
|
- 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)
|
(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)
|
- 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:
|
- 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.
|
2. Restart `headscale`, a new key will be generated.
|
||||||
3. Restart all Tailscale clients to fetch the new key
|
3. Restart all Tailscale clients to fetch the new key
|
||||||
|
|
||||||
**Changes**:
|
### Changes
|
||||||
|
|
||||||
- Unify configuration example [#197](https://github.com/juanfont/headscale/pull/197)
|
- Unify configuration example [#197](https://github.com/juanfont/headscale/pull/197)
|
||||||
- Add stricter linting and formatting [#223](https://github.com/juanfont/headscale/pull/223)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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,
|
aclPolicy ACLPolicy,
|
||||||
u string,
|
u string,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
return expandAlias(machines, aclPolicy, u)
|
return expandAlias(machines, aclPolicy, u, h.cfg.OIDC.StripEmaildomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicyDestPorts(
|
func (h *Headscale) generateACLPolicyDestPorts(
|
||||||
|
@ -186,7 +186,12 @@ func (h *Headscale) generateACLPolicyDestPorts(
|
||||||
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -218,6 +223,7 @@ func expandAlias(
|
||||||
machines []Machine,
|
machines []Machine,
|
||||||
aclPolicy ACLPolicy,
|
aclPolicy ACLPolicy,
|
||||||
alias string,
|
alias string,
|
||||||
|
stripEmailDomain bool,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
ips := []string{}
|
ips := []string{}
|
||||||
if alias == "*" {
|
if alias == "*" {
|
||||||
|
@ -225,7 +231,7 @@ func expandAlias(
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(alias, "group:") {
|
if strings.HasPrefix(alias, "group:") {
|
||||||
namespaces, err := expandGroup(aclPolicy, alias)
|
namespaces, err := expandGroup(aclPolicy, alias, stripEmailDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ips, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
@ -240,7 +246,7 @@ func expandAlias(
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(alias, "tag:") {
|
if strings.HasPrefix(alias, "tag:") {
|
||||||
owners, err := expandTagOwners(aclPolicy, alias)
|
owners, err := expandTagOwners(aclPolicy, alias, stripEmailDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ips, err
|
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
|
// expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
|
||||||
// a group cannot be composed of groups.
|
// 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
|
var owners []string
|
||||||
ows, ok := aclPolicy.TagOwners[tag]
|
ows, ok := aclPolicy.TagOwners[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -395,7 +405,7 @@ func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
|
||||||
}
|
}
|
||||||
for _, owner := range ows {
|
for _, owner := range ows {
|
||||||
if strings.HasPrefix(owner, "group:") {
|
if strings.HasPrefix(owner, "group:") {
|
||||||
gs, err := expandGroup(aclPolicy, owner)
|
gs, err := expandGroup(aclPolicy, owner, stripEmailDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
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
|
// expandGroup will return the list of namespace inside the group
|
||||||
// after some validation.
|
// after some validation.
|
||||||
func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
|
func expandGroup(
|
||||||
groups, ok := aclPolicy.Groups[group]
|
aclPolicy ACLPolicy,
|
||||||
|
group string,
|
||||||
|
stripEmailDomain bool,
|
||||||
|
) ([]string, error) {
|
||||||
|
outGroups := []string{}
|
||||||
|
aclGroups, ok := aclPolicy.Groups[group]
|
||||||
if !ok {
|
if !ok {
|
||||||
return []string{}, fmt.Errorf(
|
return []string{}, fmt.Errorf(
|
||||||
"group %v isn't registered. %w",
|
"group %v isn't registered. %w",
|
||||||
|
@ -419,14 +434,23 @@ func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
|
||||||
errInvalidGroup,
|
errInvalidGroup,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, g := range groups {
|
for _, group := range aclGroups {
|
||||||
if strings.HasPrefix(g, "group:") {
|
if strings.HasPrefix(group, "group:") {
|
||||||
return []string{}, fmt.Errorf(
|
return []string{}, fmt.Errorf(
|
||||||
"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
|
"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
|
||||||
errInvalidGroup,
|
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",
|
Name: "testmachine",
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
@ -168,7 +167,6 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
@ -215,7 +213,6 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
@ -261,7 +258,6 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
|
||||||
Name: "webserver",
|
Name: "webserver",
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
@ -281,7 +277,6 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo2),
|
HostInfo: HostInfo(hostInfo2),
|
||||||
|
@ -375,7 +370,6 @@ func (s *Suite) TestPortNamespace(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: ips,
|
IPAddresses: ips,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
|
@ -418,7 +412,6 @@ func (s *Suite) TestPortGroup(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: ips,
|
IPAddresses: ips,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
|
@ -444,8 +437,9 @@ func (s *Suite) TestPortGroup(c *check.C) {
|
||||||
|
|
||||||
func Test_expandGroup(t *testing.T) {
|
func Test_expandGroup(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
aclPolicy ACLPolicy
|
aclPolicy ACLPolicy
|
||||||
group string
|
group string
|
||||||
|
stripEmailDomain bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -462,7 +456,8 @@ func Test_expandGroup(t *testing.T) {
|
||||||
"group:foo": []string{"user2", "user3"},
|
"group:foo": []string{"user2", "user3"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
group: "group:test",
|
group: "group:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"user1", "user2", "user3"},
|
want: []string{"user1", "user2", "user3"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -476,15 +471,54 @@ func Test_expandGroup(t *testing.T) {
|
||||||
"group:foo": []string{"user2", "user3"},
|
"group:foo": []string{"user2", "user3"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
group: "group:undefined",
|
group: "group:undefined",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{},
|
want: []string{},
|
||||||
wantErr: true,
|
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 {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
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 {
|
if (err != nil) != test.wantErr {
|
||||||
t.Errorf("expandGroup() error = %v, wantErr %v", err, 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) {
|
func Test_expandTagOwners(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
aclPolicy ACLPolicy
|
aclPolicy ACLPolicy
|
||||||
tag string
|
tag string
|
||||||
|
stripEmailDomain bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -514,7 +549,8 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
TagOwners: TagOwners{"tag:test": []string{"user1"}},
|
TagOwners: TagOwners{"tag:test": []string{"user1"}},
|
||||||
},
|
},
|
||||||
tag: "tag:test",
|
tag: "tag:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"user1"},
|
want: []string{"user1"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -526,7 +562,8 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
Groups: Groups{"group:foo": []string{"user1", "user2"}},
|
Groups: Groups{"group:foo": []string{"user1", "user2"}},
|
||||||
TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
|
TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
|
||||||
},
|
},
|
||||||
tag: "tag:test",
|
tag: "tag:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"user1", "user2"},
|
want: []string{"user1", "user2"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -538,7 +575,8 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
Groups: Groups{"group:foo": []string{"user1", "user2"}},
|
Groups: Groups{"group:foo": []string{"user1", "user2"}},
|
||||||
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
|
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
|
||||||
},
|
},
|
||||||
tag: "tag:test",
|
tag: "tag:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"user1", "user2", "user3"},
|
want: []string{"user1", "user2", "user3"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -549,7 +587,8 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
|
TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
|
||||||
},
|
},
|
||||||
tag: "tag:test",
|
tag: "tag:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{},
|
want: []string{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -561,7 +600,8 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
Groups: Groups{"group:bar": []string{"user1", "user2"}},
|
Groups: Groups{"group:bar": []string{"user1", "user2"}},
|
||||||
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
|
TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
|
||||||
},
|
},
|
||||||
tag: "tag:test",
|
tag: "tag:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{},
|
want: []string{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -569,7 +609,11 @@ func Test_expandTagOwners(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
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 {
|
if (err != nil) != test.wantErr {
|
||||||
t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
|
t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
|
||||||
|
|
||||||
|
@ -731,9 +775,10 @@ func Test_listMachinesInNamespace(t *testing.T) {
|
||||||
// nolint
|
// nolint
|
||||||
func Test_expandAlias(t *testing.T) {
|
func Test_expandAlias(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
machines []Machine
|
machines []Machine
|
||||||
aclPolicy ACLPolicy
|
aclPolicy ACLPolicy
|
||||||
alias string
|
alias string
|
||||||
|
stripEmailDomain bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -753,7 +798,8 @@ func Test_expandAlias(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
aclPolicy: ACLPolicy{},
|
aclPolicy: ACLPolicy{},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"*"},
|
want: []string{"*"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -791,6 +837,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
Groups: Groups{"group:accountant": []string{"joe", "marc"}},
|
Groups: Groups{"group:accountant": []string{"joe", "marc"}},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
want: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -828,6 +875,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
Groups: Groups{"group:accountant": []string{"joe", "marc"}},
|
Groups: Groups{"group:accountant": []string{"joe", "marc"}},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{},
|
want: []string{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -835,9 +883,10 @@ func Test_expandAlias(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "simple ipaddress",
|
name: "simple ipaddress",
|
||||||
args: args{
|
args: args{
|
||||||
alias: "10.0.0.3",
|
alias: "10.0.0.3",
|
||||||
machines: []Machine{},
|
machines: []Machine{},
|
||||||
aclPolicy: ACLPolicy{},
|
aclPolicy: ACLPolicy{},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"10.0.0.3"},
|
want: []string{"10.0.0.3"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -852,6 +901,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
|
"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"192.168.1.0/24"},
|
want: []string{"192.168.1.0/24"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -859,9 +909,10 @@ func Test_expandAlias(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "simple host",
|
name: "simple host",
|
||||||
args: args{
|
args: args{
|
||||||
alias: "10.0.0.1",
|
alias: "10.0.0.1",
|
||||||
machines: []Machine{},
|
machines: []Machine{},
|
||||||
aclPolicy: ACLPolicy{},
|
aclPolicy: ACLPolicy{},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"10.0.0.1"},
|
want: []string{"10.0.0.1"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -869,9 +920,10 @@ func Test_expandAlias(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "simple CIDR",
|
name: "simple CIDR",
|
||||||
args: args{
|
args: args{
|
||||||
alias: "10.0.0.0/16",
|
alias: "10.0.0.0/16",
|
||||||
machines: []Machine{},
|
machines: []Machine{},
|
||||||
aclPolicy: ACLPolicy{},
|
aclPolicy: ACLPolicy{},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"10.0.0.0/16"},
|
want: []string{"10.0.0.0/16"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -919,6 +971,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
|
TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"100.64.0.1", "100.64.0.2"},
|
want: []string{"100.64.0.1", "100.64.0.2"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -959,6 +1012,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
"tag:accountant-webserver": []string{"group:accountant"},
|
"tag:accountant-webserver": []string{"group:accountant"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{},
|
want: []string{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -1006,6 +1060,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
aclPolicy: ACLPolicy{
|
aclPolicy: ACLPolicy{
|
||||||
TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
|
TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
|
||||||
},
|
},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"100.64.0.4"},
|
want: []string{"100.64.0.4"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
|
@ -1017,6 +1072,7 @@ func Test_expandAlias(t *testing.T) {
|
||||||
test.args.machines,
|
test.args.machines,
|
||||||
test.args.aclPolicy,
|
test.args.aclPolicy,
|
||||||
test.args.alias,
|
test.args.alias,
|
||||||
|
test.args.stripEmailDomain,
|
||||||
)
|
)
|
||||||
if (err != nil) != test.wantErr {
|
if (err != nil) != test.wantErr {
|
||||||
t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)
|
t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)
|
||||||
|
|
189
api.go
189
api.go
|
@ -22,7 +22,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
reservedResponseHeaderSize = 4
|
reservedResponseHeaderSize = 4
|
||||||
RegisterMethodAuthKey = "authKey"
|
RegisterMethodAuthKey = "authkey"
|
||||||
RegisterMethodOIDC = "oidc"
|
RegisterMethodOIDC = "oidc"
|
||||||
RegisterMethodCLI = "cli"
|
RegisterMethodCLI = "cli"
|
||||||
ErrRegisterMethodCLIDoesNotSupportExpire = Error(
|
ErrRegisterMethodCLIDoesNotSupportExpire = Error(
|
||||||
|
@ -125,25 +125,50 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
|
||||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
||||||
newMachine := Machine{
|
|
||||||
Expiry: &time.Time{},
|
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||||
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
|
||||||
Name: req.Hostinfo.Hostname,
|
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||||
}
|
if req.Auth.AuthKey != "" {
|
||||||
if err := h.db.Create(&newMachine).Error; err != nil {
|
h.handleAuthKey(ctx, machineKey, req)
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Could not create row")
|
|
||||||
machineRegistrations.WithLabelValues("unknown", "web", "error", machine.Namespace.Name).
|
|
||||||
Inc()
|
|
||||||
|
|
||||||
return
|
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
|
// 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:
|
// request, then we have a node that is either:
|
||||||
// - Trying to log out (sending a expiry in the past)
|
// - Trying to log out (sending a expiry in the past)
|
||||||
|
@ -180,15 +205,6 @@ func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
|
||||||
|
|
||||||
return
|
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(
|
func (h *Headscale) getMapResponse(
|
||||||
|
@ -402,7 +418,7 @@ func (h *Headscale) handleMachineExpired(
|
||||||
Msg("Machine registration has expired. Sending a authurl to register")
|
Msg("Machine registration has expired. Sending a authurl to register")
|
||||||
|
|
||||||
if registerRequest.Auth.AuthKey != "" {
|
if registerRequest.Auth.AuthKey != "" {
|
||||||
h.handleAuthKey(ctx, machineKey, registerRequest, machine)
|
h.handleAuthKey(ctx, machineKey, registerRequest)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -465,13 +481,12 @@ func (h *Headscale) handleMachineRegistrationNew(
|
||||||
ctx *gin.Context,
|
ctx *gin.Context,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
registerRequest tailcfg.RegisterRequest,
|
registerRequest tailcfg.RegisterRequest,
|
||||||
machine Machine,
|
|
||||||
) {
|
) {
|
||||||
resp := tailcfg.RegisterResponse{}
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
// The machine registration is new, redirect the client to the registration URL
|
// The machine registration is new, redirect the client to the registration URL
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Msg("The node is sending us a new NodeKey, sending auth url")
|
Msg("The node is sending us a new NodeKey, sending auth url")
|
||||||
if h.cfg.OIDC.Issuer != "" {
|
if h.cfg.OIDC.Issuer != "" {
|
||||||
resp.AuthURL = fmt.Sprintf(
|
resp.AuthURL = fmt.Sprintf(
|
||||||
|
@ -484,24 +499,6 @@ func (h *Headscale) handleMachineRegistrationNew(
|
||||||
strings.TrimSuffix(h.cfg.ServerURL, "/"), MachinePublicKeyStripPrefix(machineKey))
|
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)
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
@ -520,19 +517,21 @@ func (h *Headscale) handleAuthKey(
|
||||||
ctx *gin.Context,
|
ctx *gin.Context,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
registerRequest tailcfg.RegisterRequest,
|
registerRequest tailcfg.RegisterRequest,
|
||||||
machine Machine,
|
|
||||||
) {
|
) {
|
||||||
|
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||||
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||||
resp := tailcfg.RegisterResponse{}
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed authentication via AuthKey")
|
Msg("Failed authentication via AuthKey")
|
||||||
resp.MachineAuthorized = false
|
resp.MachineAuthorized = false
|
||||||
|
@ -541,76 +540,66 @@ func (h *Headscale) handleAuthKey(
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
ctx.String(http.StatusInternalServerError, "")
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody)
|
ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody)
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Msg("Failed authentication via AuthKey")
|
Msg("Failed authentication via AuthKey")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if machine.isRegistered() {
|
log.Debug().
|
||||||
log.Trace().
|
Str("func", "handleAuthKey").
|
||||||
Caller().
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Str("machine", machine.Name).
|
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||||
Msg("machine already registered, reauthenticating")
|
|
||||||
|
|
||||||
h.RefreshMachine(&machine, registerRequest.Expiry)
|
nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
} else {
|
now := time.Now().UTC()
|
||||||
log.Debug().
|
|
||||||
Str("func", "handleAuthKey").
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
|
||||||
|
|
||||||
h.ipAllocationMutex.Lock()
|
machineToRegister := Machine{
|
||||||
|
Name: registerRequest.Hostinfo.Hostname,
|
||||||
ips, err := h.getAvailableIPs()
|
NamespaceID: pak.Namespace.ID,
|
||||||
if err != nil {
|
MachineKey: machineKeyStr,
|
||||||
log.Error().
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
Caller().
|
Expiry: ®isterRequest.Expiry,
|
||||||
Str("func", "handleAuthKey").
|
NodeKey: nodeKey,
|
||||||
Str("machine", machine.Name).
|
LastSeen: &now,
|
||||||
Msg("Failed to find an available IP address")
|
AuthKeyID: uint(pak.ID),
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pak.Used = true
|
machine, err := h.RegisterMachine(
|
||||||
h.db.Save(&pak)
|
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.MachineAuthorized = true
|
||||||
resp.User = *pak.Namespace.toUser()
|
resp.User = *pak.Namespace.toUser()
|
||||||
|
@ -619,21 +608,21 @@ func (h *Headscale) handleAuthKey(
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
ctx.String(http.StatusInternalServerError, "Extremely sad!")
|
ctx.String(http.StatusInternalServerError, "Extremely sad!")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "success", machine.Namespace.Name).
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
|
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
|
||||||
Msg("Successfully authenticated via AuthKey")
|
Msg("Successfully authenticated via AuthKey")
|
||||||
}
|
}
|
||||||
|
|
29
app.go
29
app.go
|
@ -55,8 +55,8 @@ const (
|
||||||
HTTPReadTimeout = 30 * time.Second
|
HTTPReadTimeout = 30 * time.Second
|
||||||
privateKeyFileMode = 0o600
|
privateKeyFileMode = 0o600
|
||||||
|
|
||||||
requestedExpiryCacheExpiration = time.Minute * 5
|
registerCacheExpiration = time.Minute * 15
|
||||||
requestedExpiryCacheCleanupInterval = time.Minute * 10
|
registerCacheCleanup = time.Minute * 20
|
||||||
|
|
||||||
errUnsupportedDatabase = Error("unsupported DB")
|
errUnsupportedDatabase = Error("unsupported DB")
|
||||||
errUnsupportedLetsEncryptChallengeType = Error(
|
errUnsupportedLetsEncryptChallengeType = Error(
|
||||||
|
@ -148,11 +148,10 @@ type Headscale struct {
|
||||||
|
|
||||||
lastStateChange sync.Map
|
lastStateChange sync.Map
|
||||||
|
|
||||||
oidcProvider *oidc.Provider
|
oidcProvider *oidc.Provider
|
||||||
oauth2Config *oauth2.Config
|
oauth2Config *oauth2.Config
|
||||||
oidcStateCache *cache.Cache
|
|
||||||
|
|
||||||
requestedExpiryCache *cache.Cache
|
registrationCache *cache.Cache
|
||||||
|
|
||||||
ipAllocationMutex sync.Mutex
|
ipAllocationMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -202,18 +201,18 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
||||||
return nil, errUnsupportedDatabase
|
return nil, errUnsupportedDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedExpiryCache := cache.New(
|
registrationCache := cache.New(
|
||||||
requestedExpiryCacheExpiration,
|
registerCacheExpiration,
|
||||||
requestedExpiryCacheCleanupInterval,
|
registerCacheCleanup,
|
||||||
)
|
)
|
||||||
|
|
||||||
app := Headscale{
|
app := Headscale{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
dbType: cfg.DBtype,
|
dbType: cfg.DBtype,
|
||||||
dbString: dbString,
|
dbString: dbString,
|
||||||
privateKey: privKey,
|
privateKey: privKey,
|
||||||
aclRules: tailcfg.FilterAllowAll, // default allowall
|
aclRules: tailcfg.FilterAllowAll, // default allowall
|
||||||
requestedExpiryCache: requestedExpiryCache,
|
registrationCache: registrationCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = app.initDB()
|
err = app.initDB()
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
@ -50,10 +49,6 @@ func (s *Suite) ResetDB(c *check.C) {
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
dbType: "sqlite3",
|
dbType: "sqlite3",
|
||||||
dbString: tmpDir + "/headscale_test.db",
|
dbString: tmpDir + "/headscale_test.db",
|
||||||
requestedExpiryCache: cache.New(
|
|
||||||
requestedExpiryCacheExpiration,
|
|
||||||
requestedExpiryCacheCleanupInterval,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
err = app.initDB()
|
err = app.initDB()
|
||||||
if err != nil {
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
|
@ -39,6 +40,38 @@ func (h *Headscale) initDB() error {
|
||||||
|
|
||||||
_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
|
_ = 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{})
|
err = db.AutoMigrate(&Machine{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -164,7 +164,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_1",
|
Name: "test_get_shared_nodes_1",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
|
@ -182,7 +181,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_2",
|
Name: "test_get_shared_nodes_2",
|
||||||
NamespaceID: namespaceShared2.ID,
|
NamespaceID: namespaceShared2.ID,
|
||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
|
@ -200,7 +198,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_3",
|
Name: "test_get_shared_nodes_3",
|
||||||
NamespaceID: namespaceShared3.ID,
|
NamespaceID: namespaceShared3.ID,
|
||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
|
@ -218,7 +215,6 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_4",
|
Name: "test_get_shared_nodes_4",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
||||||
|
@ -311,7 +307,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_1",
|
Name: "test_get_shared_nodes_1",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
|
@ -329,7 +324,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_2",
|
Name: "test_get_shared_nodes_2",
|
||||||
NamespaceID: namespaceShared2.ID,
|
NamespaceID: namespaceShared2.ID,
|
||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
|
@ -347,7 +341,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_3",
|
Name: "test_get_shared_nodes_3",
|
||||||
NamespaceID: namespaceShared3.ID,
|
NamespaceID: namespaceShared3.ID,
|
||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
|
@ -365,7 +358,6 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_4",
|
Name: "test_get_shared_nodes_4",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
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"`
|
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"`
|
Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
Namespace *Namespace `protobuf:"bytes,7,opt,name=namespace,proto3" json:"namespace,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"`
|
LastSeen *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
|
||||||
RegisterMethod RegisterMethod `protobuf:"varint,9,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"`
|
LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"`
|
||||||
LastSeen *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
|
Expiry *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=expiry,proto3" json:"expiry,omitempty"`
|
||||||
LastSuccessfulUpdate *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=last_successful_update,json=lastSuccessfulUpdate,proto3" json:"last_successful_update,omitempty"`
|
PreAuthKey *PreAuthKey `protobuf:"bytes,11,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"`
|
||||||
Expiry *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expiry,proto3" json:"expiry,omitempty"`
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
PreAuthKey *PreAuthKey `protobuf:"bytes,13,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"`
|
RegisterMethod RegisterMethod `protobuf:"varint,13,opt,name=register_method,json=registerMethod,proto3,enum=headscale.v1.RegisterMethod" json:"register_method,omitempty"`
|
||||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Machine) Reset() {
|
func (x *Machine) Reset() {
|
||||||
|
@ -175,20 +174,6 @@ func (x *Machine) GetNamespace() *Namespace {
|
||||||
return nil
|
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 {
|
func (x *Machine) GetLastSeen() *timestamppb.Timestamp {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.LastSeen
|
return x.LastSeen
|
||||||
|
@ -224,6 +209,13 @@ func (x *Machine) GetCreatedAt() *timestamppb.Timestamp {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Machine) GetRegisterMethod() RegisterMethod {
|
||||||
|
if x != nil {
|
||||||
|
return x.RegisterMethod
|
||||||
|
}
|
||||||
|
return RegisterMethod_REGISTER_METHOD_UNSPECIFIED
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterMachineRequest struct {
|
type RegisterMachineRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x6c,
|
||||||
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||||
0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72,
|
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||||
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09,
|
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74,
|
||||||
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
|
0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x09,
|
||||||
0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18,
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||||
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79,
|
||||||
0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c,
|
0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||||
0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72,
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b,
|
||||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63,
|
0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a,
|
0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41,
|
||||||
0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x64, 0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72,
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||||
0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65,
|
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
|
||||||
0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
0x74, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65,
|
||||||
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
|
0x74, 0x68, 0x6f, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61,
|
||||||
0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a,
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||||
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69,
|
||||||
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,
|
|
||||||
0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
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,
|
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,
|
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{
|
var file_headscale_v1_machine_proto_depIdxs = []int32{
|
||||||
14, // 0: headscale.v1.Machine.namespace:type_name -> headscale.v1.Namespace
|
14, // 0: headscale.v1.Machine.namespace:type_name -> headscale.v1.Namespace
|
||||||
0, // 1: headscale.v1.Machine.register_method:type_name -> headscale.v1.RegisterMethod
|
15, // 1: headscale.v1.Machine.last_seen:type_name -> google.protobuf.Timestamp
|
||||||
15, // 2: 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.last_successful_update:type_name -> google.protobuf.Timestamp
|
15, // 3: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
|
||||||
15, // 4: headscale.v1.Machine.expiry:type_name -> google.protobuf.Timestamp
|
16, // 4: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
|
||||||
16, // 5: headscale.v1.Machine.pre_auth_key:type_name -> headscale.v1.PreAuthKey
|
15, // 5: headscale.v1.Machine.created_at:type_name -> google.protobuf.Timestamp
|
||||||
15, // 6: 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, // 7: headscale.v1.RegisterMachineResponse.machine:type_name -> headscale.v1.Machine
|
||||||
1, // 8: headscale.v1.GetMachineResponse.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
|
1, // 9: headscale.v1.ExpireMachineResponse.machine:type_name -> headscale.v1.Machine
|
||||||
|
|
|
@ -885,12 +885,6 @@
|
||||||
"namespace": {
|
"namespace": {
|
||||||
"$ref": "#/definitions/v1Namespace"
|
"$ref": "#/definitions/v1Namespace"
|
||||||
},
|
},
|
||||||
"registered": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"registerMethod": {
|
|
||||||
"$ref": "#/definitions/v1RegisterMethod"
|
|
||||||
},
|
|
||||||
"lastSeen": {
|
"lastSeen": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
@ -909,6 +903,9 @@
|
||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"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("namespace", request.GetNamespace()).
|
||||||
Str("machine_key", request.GetKey()).
|
Str("machine_key", request.GetKey()).
|
||||||
Msg("Registering machine")
|
Msg("Registering machine")
|
||||||
machine, err := api.h.RegisterMachine(
|
|
||||||
|
machine, err := api.h.RegisterMachineFromAuthCallback(
|
||||||
request.GetKey(),
|
request.GetKey(),
|
||||||
request.GetNamespace(),
|
request.GetNamespace(),
|
||||||
|
RegisterMethodCLI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -379,11 +381,11 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
||||||
HostInfo: HostInfo(hostinfo),
|
HostInfo: HostInfo(hostinfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.Trace().Caller().Interface("machine", newMachine).Msg("")
|
api.h.registrationCache.Set(
|
||||||
|
request.GetKey(),
|
||||||
if err := api.h.db.Create(&newMachine).Error; err != nil {
|
newMachine,
|
||||||
return nil, err
|
registerCacheExpiration,
|
||||||
}
|
)
|
||||||
|
|
||||||
return &v1.DebugCreateMachineResponse{Machine: newMachine.toProto()}, nil
|
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-4", listAll[3].Name)
|
||||||
assert.Equal(s.T(), "machine-5", listAll[4].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{
|
otherNamespaceMachineKeys := []string{
|
||||||
"b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e",
|
"b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e",
|
||||||
"dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584",
|
"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-1", listAllWithotherNamespace[5].Name)
|
||||||
assert.Equal(s.T(), "otherNamespace-machine-2", listAllWithotherNamespace[6].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
|
// Test list all nodes after added otherNamespace
|
||||||
listOnlyotherNamespaceMachineNamespaceResult, err := ExecuteCommand(
|
listOnlyotherNamespaceMachineNamespaceResult, err := ExecuteCommand(
|
||||||
&s.headscale,
|
&s.headscale,
|
||||||
|
@ -752,9 +743,6 @@ func (s *IntegrationCLITestSuite) TestNodeCommand() {
|
||||||
listOnlyotherNamespaceMachineNamespace[1].Name,
|
listOnlyotherNamespaceMachineNamespace[1].Name,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[0].Registered)
|
|
||||||
assert.True(s.T(), listOnlyotherNamespaceMachineNamespace[1].Registered)
|
|
||||||
|
|
||||||
// Delete a machines
|
// Delete a machines
|
||||||
_, err = ExecuteCommand(
|
_, err = ExecuteCommand(
|
||||||
&s.headscale,
|
&s.headscale,
|
||||||
|
@ -979,7 +967,6 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
|
||||||
|
|
||||||
assert.Equal(s.T(), uint64(1), machine.Id)
|
assert.Equal(s.T(), uint64(1), machine.Id)
|
||||||
assert.Equal(s.T(), "route-machine", machine.Name)
|
assert.Equal(s.T(), "route-machine", machine.Name)
|
||||||
assert.True(s.T(), machine.Registered)
|
|
||||||
|
|
||||||
listAllResult, err := ExecuteCommand(
|
listAllResult, err := ExecuteCommand(
|
||||||
&s.headscale,
|
&s.headscale,
|
||||||
|
|
150
machine.go
150
machine.go
|
@ -18,11 +18,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errMachineNotFound = Error("machine not found")
|
errMachineNotFound = Error("machine not found")
|
||||||
errMachineAlreadyRegistered = Error("machine already registered")
|
errMachineRouteIsNotAvailable = Error("route is not available on machine")
|
||||||
errMachineRouteIsNotAvailable = Error("route is not available on machine")
|
errMachineAddressesInvalid = Error("failed to parse machine addresses")
|
||||||
errMachineAddressesInvalid = Error("failed to parse machine addresses")
|
errMachineNotFoundRegistrationCache = Error(
|
||||||
errHostnameTooLong = Error("Hostname too long")
|
"machine not found in registration cache",
|
||||||
|
)
|
||||||
|
errCouldNotConvertMachineInterface = Error("failed to convert machine interface")
|
||||||
|
errHostnameTooLong = Error("Hostname too long")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,10 +43,11 @@ type Machine struct {
|
||||||
NamespaceID uint
|
NamespaceID uint
|
||||||
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
||||||
|
|
||||||
Registered bool // temp
|
|
||||||
RegisterMethod string
|
RegisterMethod string
|
||||||
AuthKeyID uint
|
|
||||||
AuthKey *PreAuthKey
|
// TODO(kradalby): This seems like irrelevant information?
|
||||||
|
AuthKeyID uint
|
||||||
|
AuthKey *PreAuthKey
|
||||||
|
|
||||||
LastSeen *time.Time
|
LastSeen *time.Time
|
||||||
LastSuccessfulUpdate *time.Time
|
LastSuccessfulUpdate *time.Time
|
||||||
|
@ -63,11 +67,6 @@ type (
|
||||||
MachinesP []*Machine
|
MachinesP []*Machine
|
||||||
)
|
)
|
||||||
|
|
||||||
// For the time being this method is rather naive.
|
|
||||||
func (machine Machine) isRegistered() bool {
|
|
||||||
return machine.Registered
|
|
||||||
}
|
|
||||||
|
|
||||||
type MachineAddresses []netaddr.IP
|
type MachineAddresses []netaddr.IP
|
||||||
|
|
||||||
func (ma MachineAddresses) ToStringSlice() []string {
|
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
|
// If Expiry is not set, the client has not indicated that
|
||||||
// it wants an expiry time, it is therefor considered
|
// it wants an expiry time, it is therefor considered
|
||||||
// to mean "not expired"
|
// to mean "not expired"
|
||||||
if machine.Expiry.IsZero() {
|
if machine.Expiry == nil || machine.Expiry.IsZero() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +170,12 @@ func getFilteredByACLPeers(
|
||||||
machine.IPAddresses.ToStringSlice(),
|
machine.IPAddresses.ToStringSlice(),
|
||||||
peer.IPAddresses.ToStringSlice(),
|
peer.IPAddresses.ToStringSlice(),
|
||||||
) || // match source and destination
|
) || // match source and destination
|
||||||
|
matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
peer.IPAddresses.ToStringSlice(),
|
||||||
|
machine.IPAddresses.ToStringSlice(),
|
||||||
|
) || // match return path
|
||||||
matchSourceAndDestinationWithRule(
|
matchSourceAndDestinationWithRule(
|
||||||
rule.SrcIPs,
|
rule.SrcIPs,
|
||||||
dst,
|
dst,
|
||||||
|
@ -180,9 +185,21 @@ func getFilteredByACLPeers(
|
||||||
matchSourceAndDestinationWithRule(
|
matchSourceAndDestinationWithRule(
|
||||||
rule.SrcIPs,
|
rule.SrcIPs,
|
||||||
dst,
|
dst,
|
||||||
|
[]string{"*"},
|
||||||
|
[]string{"*"},
|
||||||
|
) || // match source and all destination
|
||||||
|
matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
[]string{"*"},
|
||||||
peer.IPAddresses.ToStringSlice(),
|
peer.IPAddresses.ToStringSlice(),
|
||||||
|
) || // match source and all destination
|
||||||
|
matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
[]string{"*"},
|
||||||
machine.IPAddresses.ToStringSlice(),
|
machine.IPAddresses.ToStringSlice(),
|
||||||
) { // match return path
|
) { // match all sources and source
|
||||||
peers[peer.ID] = peer
|
peers[peer.ID] = peer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +229,7 @@ func (h *Headscale) ListPeers(machine *Machine) (Machines, error) {
|
||||||
Msg("Finding direct peers")
|
Msg("Finding direct peers")
|
||||||
|
|
||||||
machines := Machines{}
|
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 {
|
machine.MachineKey).Find(&machines).Error; err != nil {
|
||||||
log.Error().Err(err).Msg("Error accessing db")
|
log.Error().Err(err).Msg("Error accessing db")
|
||||||
|
|
||||||
|
@ -275,7 +292,7 @@ func (h *Headscale) getValidPeers(machine *Machine) (Machines, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
if peer.isRegistered() && !peer.isExpired() {
|
if !peer.isExpired() {
|
||||||
validPeers = append(validPeers, peer)
|
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.
|
// DeleteMachine softs deletes a Machine from the database.
|
||||||
func (h *Headscale) DeleteMachine(machine *Machine) error {
|
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 {
|
if err := h.db.Delete(&machine).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -581,7 +596,7 @@ func (machine Machine) toNode(
|
||||||
LastSeen: machine.LastSeen,
|
LastSeen: machine.LastSeen,
|
||||||
|
|
||||||
KeepAlive: true,
|
KeepAlive: true,
|
||||||
MachineAuthorized: machine.Registered,
|
MachineAuthorized: !machine.isExpired(),
|
||||||
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,8 +614,6 @@ func (machine *Machine) toProto() *v1.Machine {
|
||||||
Name: machine.Name,
|
Name: machine.Name,
|
||||||
Namespace: machine.Namespace.toProto(),
|
Namespace: machine.Namespace.toProto(),
|
||||||
|
|
||||||
Registered: machine.Registered,
|
|
||||||
|
|
||||||
// TODO(kradalby): Implement register method enum converter
|
// TODO(kradalby): Implement register method enum converter
|
||||||
// RegisterMethod: ,
|
// RegisterMethod: ,
|
||||||
|
|
||||||
|
@ -628,74 +641,50 @@ func (machine *Machine) toProto() *v1.Machine {
|
||||||
return machineProto
|
return machineProto
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
func (h *Headscale) RegisterMachineFromAuthCallback(
|
||||||
func (h *Headscale) RegisterMachine(
|
|
||||||
machineKeyStr string,
|
machineKeyStr string,
|
||||||
namespaceName string,
|
namespaceName string,
|
||||||
|
registrationMethod string,
|
||||||
) (*Machine, error) {
|
) (*Machine, error) {
|
||||||
namespace, err := h.GetNamespace(namespaceName)
|
if machineInterface, ok := h.registrationCache.Get(machineKeyStr); ok {
|
||||||
if err != nil {
|
if registrationMachine, ok := machineInterface.(Machine); ok {
|
||||||
return nil, err
|
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
|
registrationMachine.NamespaceID = namespace.ID
|
||||||
err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
registrationMachine.RegisterMethod = registrationMethod
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace().
|
machine, err := h.RegisterMachine(
|
||||||
Caller().
|
registrationMachine,
|
||||||
Str("machine_key_str", machineKeyStr).
|
)
|
||||||
Str("machine_key", machineKey.String()).
|
|
||||||
Msg("Registering machine")
|
|
||||||
|
|
||||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
return machine, err
|
||||||
if err != nil {
|
} else {
|
||||||
return nil, err
|
return nil, errCouldNotConvertMachineInterface
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if machine.isRegistered() {
|
return nil, errMachineNotFoundRegistrationCache
|
||||||
log.Trace().
|
}
|
||||||
Caller().
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Msg("machine already registered, reauthenticating")
|
|
||||||
|
|
||||||
h.RefreshMachine(machine, requestedTime)
|
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
||||||
|
func (h *Headscale) RegisterMachine(machine Machine,
|
||||||
return machine, nil
|
) (*Machine, error) {
|
||||||
}
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("machine_key", machine.MachineKey).
|
||||||
|
Msg("Registering machine")
|
||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Attempting to register machine")
|
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()
|
h.ipAllocationMutex.Lock()
|
||||||
defer h.ipAllocationMutex.Unlock()
|
defer h.ipAllocationMutex.Unlock()
|
||||||
|
|
||||||
|
@ -710,17 +699,8 @@ func (h *Headscale) RegisterMachine(
|
||||||
return nil, err
|
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.IPAddresses = ips
|
||||||
machine.NamespaceID = namespace.ID
|
|
||||||
machine.Registered = true
|
|
||||||
machine.RegisterMethod = RegisterMethodCLI
|
|
||||||
machine.Expiry = &requestedTime
|
|
||||||
h.db.Save(&machine)
|
h.db.Save(&machine)
|
||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
|
@ -729,7 +709,7 @@ func (h *Headscale) RegisterMachine(
|
||||||
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
|
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
|
||||||
Msg("Machine registered with the database")
|
Msg("Machine registered with the database")
|
||||||
|
|
||||||
return machine, nil
|
return &machine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (machine *Machine) GetAdvertisedRoutes() []netaddr.IPPrefix {
|
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",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -56,7 +55,6 @@ func (s *Suite) TestGetMachineByID(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -76,7 +74,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(1),
|
AuthKeyID: uint(1),
|
||||||
}
|
}
|
||||||
|
@ -99,7 +96,6 @@ func (s *Suite) TestHardDeleteMachine(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine3",
|
Name: "testmachine3",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(1),
|
AuthKeyID: uint(1),
|
||||||
}
|
}
|
||||||
|
@ -130,7 +126,6 @@ func (s *Suite) TestListPeers(c *check.C) {
|
||||||
DiscoKey: "faa" + strconv.Itoa(index),
|
DiscoKey: "faa" + strconv.Itoa(index),
|
||||||
Name: "testmachine" + strconv.Itoa(index),
|
Name: "testmachine" + strconv.Itoa(index),
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -179,7 +174,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||||
},
|
},
|
||||||
Name: "testmachine" + strconv.Itoa(index),
|
Name: "testmachine" + strconv.Itoa(index),
|
||||||
NamespaceID: stor[index%2].namespace.ID,
|
NamespaceID: stor[index%2].namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(stor[index%2].key.ID),
|
AuthKeyID: uint(stor[index%2].key.ID),
|
||||||
}
|
}
|
||||||
|
@ -246,7 +240,6 @@ func (s *Suite) TestExpireMachine(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
Expiry: &time.Time{},
|
Expiry: &time.Time{},
|
||||||
|
@ -284,6 +277,7 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint
|
||||||
func Test_getFilteredByACLPeers(t *testing.T) {
|
func Test_getFilteredByACLPeers(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
machines []Machine
|
machines []Machine
|
||||||
|
@ -431,7 +425,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
machine: &Machine{ // current machine
|
machine: &Machine{ // current machine
|
||||||
ID: 1,
|
ID: 2,
|
||||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||||
Namespace: Namespace{Name: "marc"},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -54,7 +54,6 @@ func (s *Suite) TestDestroyNamespaceErrors(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -146,7 +145,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_1",
|
Name: "test_get_shared_nodes_1",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyShared1.ID),
|
AuthKeyID: uint(preAuthKeyShared1.ID),
|
||||||
|
@ -164,7 +162,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_2",
|
Name: "test_get_shared_nodes_2",
|
||||||
NamespaceID: namespaceShared2.ID,
|
NamespaceID: namespaceShared2.ID,
|
||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyShared2.ID),
|
AuthKeyID: uint(preAuthKeyShared2.ID),
|
||||||
|
@ -182,7 +179,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_3",
|
Name: "test_get_shared_nodes_3",
|
||||||
NamespaceID: namespaceShared3.ID,
|
NamespaceID: namespaceShared3.ID,
|
||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyShared3.ID),
|
AuthKeyID: uint(preAuthKeyShared3.ID),
|
||||||
|
@ -200,7 +196,6 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||||
Name: "test_get_shared_nodes_4",
|
Name: "test_get_shared_nodes_4",
|
||||||
NamespaceID: namespaceShared1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
||||||
|
|
137
oidc.go
137
oidc.go
|
@ -10,21 +10,16 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/patrickmn/go-cache"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"gorm.io/gorm"
|
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
oidcStateCacheExpiration = time.Minute * 5
|
randomByteSize = 16
|
||||||
oidcStateCacheCleanupInterval = time.Minute * 10
|
|
||||||
randomByteSize = 16
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDTokenClaims struct {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +88,7 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) {
|
||||||
stateStr := hex.EncodeToString(randomBlob)[:32]
|
stateStr := hex.EncodeToString(randomBlob)[:32]
|
||||||
|
|
||||||
// place the machine key into the state cache, so it can be retrieved later
|
// 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)
|
authURL := h.oauth2Config.AuthCodeURL(stateStr)
|
||||||
log.Debug().Msgf("Redirecting to %s for authentication", authURL)
|
log.Debug().Msgf("Redirecting to %s for authentication", authURL)
|
||||||
|
@ -125,7 +112,6 @@ var oidcCallbackTemplate = template.Must(
|
||||||
</html>`),
|
</html>`),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Why is the entire machine registration logic duplicated here?
|
|
||||||
// OIDCCallback handles the callback from the OIDC endpoint
|
// OIDCCallback handles the callback from the OIDC endpoint
|
||||||
// Retrieves the mkey from the state cache and adds the machine to the users email namespace
|
// 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
|
// 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
|
// retrieve machinekey from state cache
|
||||||
machineKeyIf, machineKeyFound := h.oidcStateCache.Get(state)
|
machineKeyIf, machineKeyFound := h.registrationCache.Get(state)
|
||||||
|
|
||||||
if !machineKeyFound {
|
if !machineKeyFound {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
@ -207,10 +193,12 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
machineKeyStr, machineKeyOK := machineKeyIf.(string)
|
machineKeyFromCache, machineKeyOK := machineKeyIf.(string)
|
||||||
|
|
||||||
var machineKey key.MachinePublic
|
var machineKey key.MachinePublic
|
||||||
err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
err = machineKey.UnmarshalText(
|
||||||
|
[]byte(MachinePublicKeyEnsurePrefix(machineKeyFromCache)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Msg("could not parse machine public key")
|
Msg("could not parse machine public key")
|
||||||
|
@ -229,33 +217,19 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
|
// retrieve machine information if it exist
|
||||||
requestedTime := time.Time{}
|
// The error is not important, because if it does not
|
||||||
if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found {
|
// exist, then this is a new machine and we will move
|
||||||
if reqTime, ok := requestedTimeIf.(time.Time); ok {
|
// on to registration.
|
||||||
requestedTime = reqTime
|
machine, _ := h.GetMachineByMachineKey(machineKey)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve machine information
|
if machine != nil {
|
||||||
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() {
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("machine already registered, reauthenticating")
|
Msg("machine already registered, reauthenticating")
|
||||||
|
|
||||||
h.RefreshMachine(machine, requestedTime)
|
h.RefreshMachine(machine, *machine.Expiry)
|
||||||
|
|
||||||
var content bytes.Buffer
|
var content bytes.Buffer
|
||||||
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
|
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
|
||||||
|
@ -279,8 +253,6 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
|
|
||||||
namespaceName, err := NormalizeNamespaceName(
|
namespaceName, err := NormalizeNamespaceName(
|
||||||
claims.Email,
|
claims.Email,
|
||||||
h.cfg.OIDC.StripEmaildomain,
|
h.cfg.OIDC.StripEmaildomain,
|
||||||
|
@ -294,61 +266,58 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// register the machine if it's new
|
// 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)
|
namespace, err := h.GetNamespace(namespaceName)
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, errNamespaceNotFound) {
|
||||||
namespace, err = h.CreateNamespace(namespaceName)
|
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 {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("could not get an IP from the pool")
|
Caller().
|
||||||
|
Msgf("could not create new namespace '%s'", namespaceName)
|
||||||
ctx.String(
|
ctx.String(
|
||||||
http.StatusInternalServerError,
|
http.StatusInternalServerError,
|
||||||
"could not get an IP from the pool",
|
"could not create new namespace",
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
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
|
return
|
||||||
machine.NamespaceID = namespace.ID
|
}
|
||||||
machine.Registered = true
|
|
||||||
machine.RegisterMethod = RegisterMethodOIDC
|
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||||
machine.LastSuccessfulUpdate = &now
|
|
||||||
machine.Expiry = &requestedTime
|
_, err = h.RegisterMachineFromAuthCallback(
|
||||||
h.db.Save(&machine)
|
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
|
var content bytes.Buffer
|
||||||
|
|
|
@ -113,6 +113,12 @@ func (h *Headscale) ExpirePreAuthKey(k *PreAuthKey) error {
|
||||||
return nil
|
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
|
// 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.
|
// If returns no error and a PreAuthKey, it can be used.
|
||||||
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||||
|
|
|
@ -80,7 +80,6 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testest",
|
Name: "testest",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -105,7 +104,6 @@ func (*Suite) TestReusableBeingUsedKey(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testest",
|
Name: "testest",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
@ -143,7 +141,6 @@ func (*Suite) TestEphemeralKey(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testest",
|
Name: "testest",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
LastSeen: &now,
|
LastSeen: &now,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
|
|
|
@ -22,16 +22,16 @@ message Machine {
|
||||||
string name = 6;
|
string name = 6;
|
||||||
Namespace namespace = 7;
|
Namespace namespace = 7;
|
||||||
|
|
||||||
bool registered = 8;
|
|
||||||
RegisterMethod register_method = 9;
|
|
||||||
|
|
||||||
google.protobuf.Timestamp last_seen = 10;
|
google.protobuf.Timestamp last_seen = 8;
|
||||||
google.protobuf.Timestamp last_successful_update = 11;
|
google.protobuf.Timestamp last_successful_update = 9;
|
||||||
google.protobuf.Timestamp expiry = 12;
|
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 updated_at = 14;
|
||||||
// google.protobuf.Timestamp deleted_at = 15;
|
// google.protobuf.Timestamp deleted_at = 15;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "test_get_route_machine",
|
Name: "test_get_route_machine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
@ -82,7 +81,6 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "test_enable_route_machine",
|
Name: "test_enable_route_machine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
HostInfo: HostInfo(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
|
|
|
@ -36,7 +36,6 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
IPAddresses: ips,
|
IPAddresses: ips,
|
||||||
|
@ -85,7 +84,6 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
IPAddresses: ips,
|
IPAddresses: ips,
|
||||||
|
@ -176,7 +174,6 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue