mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-23 09:37:33 +00:00
Compare commits
192 Commits
v0.15.0-be
...
mandatory-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cd2914ab3b | ||
![]() |
e85b97143c | ||
![]() |
db9ba17920 | ||
![]() |
d5ce7d7523 | ||
![]() |
2e6687209b | ||
![]() |
b781446e86 | ||
![]() |
1c9b1c0579 | ||
![]() |
ade9552736 | ||
![]() |
68403cb76e | ||
![]() |
537ecb8db0 | ||
![]() |
8f5875efe4 | ||
![]() |
98ac88d5ef | ||
![]() |
d13338a9fb | ||
![]() |
1579ffb66a | ||
![]() |
0bfa5302a7 | ||
![]() |
b8aad5451d | ||
![]() |
61440c42d3 | ||
![]() |
18ee6274e1 | ||
![]() |
0abfbdc18a | ||
![]() |
082a852c5e | ||
![]() |
af081e9fd3 | ||
![]() |
8b5e8b7dfc | ||
![]() |
62d7fae056 | ||
![]() |
dd219d0ff6 | ||
![]() |
6087e1cf6f | ||
![]() |
c47fb1ae54 | ||
![]() |
48cec3cd90 | ||
![]() |
e54c508c10 | ||
![]() |
941e9d9b0f | ||
![]() |
b803240dc1 | ||
![]() |
bdbf620ece | ||
![]() |
e5d22b8a70 | ||
![]() |
05c5e2280b | ||
![]() |
b41d89946a | ||
![]() |
cc0c88a63a | ||
![]() |
c06689dec1 | ||
![]() |
b85dd7abbd | ||
![]() |
6aeaff43aa | ||
![]() |
dd26cbd193 | ||
![]() |
b0ae3240fd | ||
![]() |
41efe98953 | ||
![]() |
2b68c90778 | ||
![]() |
f19c048569 | ||
![]() |
6cc8bbc24f | ||
![]() |
03452a8dca | ||
![]() |
15ed71315c | ||
![]() |
05df8e947a | ||
![]() |
b3fa66dbd2 | ||
![]() |
a27b386123 | ||
![]() |
580db9b58f | ||
![]() |
1114449601 | ||
![]() |
b47de07eea | ||
![]() |
e1fcf0da26 | ||
![]() |
dcf3ea567c | ||
![]() |
de2ea83b3b | ||
![]() |
eb06054a7b | ||
![]() |
eb500155e8 | ||
![]() |
dc909ba6d7 | ||
![]() |
70910c4595 | ||
![]() |
54c3e00a1f | ||
![]() |
e78c002f5a | ||
![]() |
237f7f1027 | ||
![]() |
992efbd84a | ||
![]() |
e9eb90fa76 | ||
![]() |
88378c22fb | ||
![]() |
b742379627 | ||
![]() |
df37d1a639 | ||
![]() |
758b1ba1cb | ||
![]() |
435ee36d78 | ||
![]() |
35efd8f95a | ||
![]() |
09d78c7a05 | ||
![]() |
60655c5242 | ||
![]() |
22d2443281 | ||
![]() |
a70669fca7 | ||
![]() |
0720473033 | ||
![]() |
e799307e74 | ||
![]() |
575f33d183 | ||
![]() |
607c1eb316 | ||
![]() |
d69dada8ff | ||
![]() |
f9e0c13890 | ||
![]() |
12a50ac8ac | ||
![]() |
b342cf0240 | ||
![]() |
e3ff87b7ef | ||
![]() |
745696b310 | ||
![]() |
23cde8445f | ||
![]() |
9d43f589ae | ||
![]() |
897d480f4d | ||
![]() |
6f172a6e4c | ||
![]() |
44a5372c53 | ||
![]() |
f2ea6fb30f | ||
![]() |
4a4952899b | ||
![]() |
b72a8aa7d1 | ||
![]() |
e301d0d1df | ||
![]() |
75ca91b0f7 | ||
![]() |
e208ccc982 | ||
![]() |
71a62697aa | ||
![]() |
f9c0597875 | ||
![]() |
aa3eb5171a | ||
![]() |
dcc46af8de | ||
![]() |
b61500670c | ||
![]() |
ccec534e19 | ||
![]() |
9b10457209 | ||
![]() |
9a8f605cba | ||
![]() |
1246267ead | ||
![]() |
a0a56d43f8 | ||
![]() |
63d87110f6 | ||
![]() |
7c99d963e2 | ||
![]() |
a614f158be | ||
![]() |
2b6a5173da | ||
![]() |
32ac690494 | ||
![]() |
0835bffc3c | ||
![]() |
c80e364f02 | ||
![]() |
5b169010be | ||
![]() |
eeded85d9c | ||
![]() |
e4d81bbb16 | ||
![]() |
1f8c7f427b | ||
![]() |
ef422e6988 | ||
![]() |
ec4dc68524 | ||
![]() |
86ade72c19 | ||
![]() |
0c0653df8b | ||
![]() |
12b3b5f8f1 | ||
![]() |
052dbfe440 | ||
![]() |
5310f8692b | ||
![]() |
aff6b84250 | ||
![]() |
21eee912a3 | ||
![]() |
dbb2af0238 | ||
![]() |
77fe0b01f7 | ||
![]() |
361b4f7f4f | ||
![]() |
dec4ee5f73 | ||
![]() |
b2dca80e7a | ||
![]() |
a455a874ad | ||
![]() |
49cd761bf6 | ||
![]() |
6477e6a583 | ||
![]() |
8a95fe517a | ||
![]() |
a9d4fa89dc | ||
![]() |
94c5474212 | ||
![]() |
d34d617935 | ||
![]() |
573008757d | ||
![]() |
4c74043f72 | ||
![]() |
0551b34de5 | ||
![]() |
105812421e | ||
![]() |
4a9fd3a680 | ||
![]() |
1cb39d914c | ||
![]() |
5157f356cb | ||
![]() |
7c63412df5 | ||
![]() |
82cb6b9ddc | ||
![]() |
379017602c | ||
![]() |
8bef04d8df | ||
![]() |
5e92ddad43 | ||
![]() |
e64bee778f | ||
![]() |
5e1b12948e | ||
![]() |
eea8e7ba6f | ||
![]() |
78251ce8ec | ||
![]() |
a8649d83c4 | ||
![]() |
16b21e8158 | ||
![]() |
35616eb861 | ||
![]() |
e7bef56718 | ||
![]() |
c6b87de959 | ||
![]() |
50053e616a | ||
![]() |
54cc3c067f | ||
![]() |
402a76070f | ||
![]() |
9a61725e9f | ||
![]() |
6126d6d9b5 | ||
![]() |
469551bc5d | ||
![]() |
1caa6f5d69 | ||
![]() |
ecc26432fd | ||
![]() |
caffbd8956 | ||
![]() |
fd1e4a1dcd | ||
![]() |
acb945841c | ||
![]() |
c58ce6f60c | ||
![]() |
d6f6939c54 | ||
![]() |
e0b9a317f4 | ||
![]() |
c159eb7541 | ||
![]() |
8a3a0b6403 | ||
![]() |
67d6c8f946 | ||
![]() |
06e6c29a5b | ||
![]() |
a9122c3de3 | ||
![]() |
d55c79e75b | ||
![]() |
d27f2bc538 | ||
![]() |
e3bcc88880 | ||
![]() |
14e49885fb | ||
![]() |
fbc1843889 | ||
![]() |
45d5ab30ff | ||
![]() |
d5fd7a5c00 | ||
![]() |
397b6fc4bf | ||
![]() |
55d746d3f5 | ||
![]() |
c364c2a382 | ||
![]() |
e540679dbd | ||
![]() |
86b329d8bf | ||
![]() |
7bdd7748e4 | ||
![]() |
0426212348 | ||
![]() |
85cf443ac6 |
@@ -3,6 +3,7 @@
|
|||||||
// development
|
// development
|
||||||
integration_test.go
|
integration_test.go
|
||||||
integration_test/
|
integration_test/
|
||||||
|
!integration_test/etc_embedded_derp/tls/server.crt
|
||||||
|
|
||||||
Dockerfile*
|
Dockerfile*
|
||||||
docker-compose*
|
docker-compose*
|
||||||
|
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@@ -1,10 +1,10 @@
|
|||||||
<!-- Please tick if the following things apply. You… -->
|
<!-- Please tick if the following things apply. You… -->
|
||||||
|
|
||||||
- [] read the [CONTRIBUTING guidelines](README.md#user-content-contributing)
|
- [ ] read the [CONTRIBUTING guidelines](README.md#user-content-contributing)
|
||||||
- [] raised a GitHub issue or discussed it on the projects chat beforehand
|
- [ ] raised a GitHub issue or discussed it on the projects chat beforehand
|
||||||
- [] added unit tests
|
- [ ] added unit tests
|
||||||
- [] added integration tests
|
- [ ] added integration tests
|
||||||
- [] updated documentation if needed
|
- [ ] updated documentation if needed
|
||||||
- [] updated CHANGELOG.md
|
- [ ] updated CHANGELOG.md
|
||||||
|
|
||||||
<!-- If applicable, please reference the issue using `Fixes #XXX` and add tests to cover your new code. -->
|
<!-- If applicable, please reference the issue using `Fixes #XXX` and add tests to cover your new code. -->
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.7"
|
go-version: "1.18.0"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
11
.github/workflows/contributors.yml
vendored
11
.github/workflows/contributors.yml
vendored
@@ -10,6 +10,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- name: Delete upstream contributor branch
|
||||||
|
# Allow continue on failure to account for when the
|
||||||
|
# upstream branch is deleted or does not exist.
|
||||||
|
continue-on-error: true
|
||||||
|
run: git push origin --delete update-contributors
|
||||||
|
- name: Create up-to-date contributors branch
|
||||||
|
run: git checkout -B update-contributors
|
||||||
|
- name: Push empty contributors branch
|
||||||
|
run: git push origin update-contributors
|
||||||
|
- name: Switch back to main
|
||||||
|
run: git checkout main
|
||||||
- uses: BobAnkh/add-contributors@v0.2.2
|
- uses: BobAnkh/add-contributors@v0.2.2
|
||||||
with:
|
with:
|
||||||
CONTRIBUTOR: "## Contributors"
|
CONTRIBUTOR: "## Contributors"
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.17.7
|
go-version: 1.18.0
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
2
.github/workflows/test-integration.yml
vendored
2
.github/workflows/test-integration.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.7"
|
go-version: "1.18.0"
|
||||||
|
|
||||||
- name: Run Integration tests
|
- name: Run Integration tests
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.7"
|
go-version: "1.18.0"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
@@ -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
|
||||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@@ -1,27 +1,46 @@
|
|||||||
# 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).
|
||||||
|
- `/metrics` is now a configurable host:port endpoint: [#344](https://github.com/juanfont/headscale/pull/344). You must update your `config.yaml` file to include:
|
||||||
|
```yaml
|
||||||
|
metrics_listen_addr: 127.0.0.1:9090
|
||||||
|
```
|
||||||
|
|
||||||
**Changes**:
|
### Features
|
||||||
|
|
||||||
|
- Add support for writing ACL files with YAML [#359](https://github.com/juanfont/headscale/pull/359)
|
||||||
|
- Users can now use emails in ACL's groups [#372](https://github.com/juanfont/headscale/issues/372)
|
||||||
|
- Add shorthand aliases for commands and subcommands [#376](https://github.com/juanfont/headscale/pull/376)
|
||||||
|
- Add `/windows` endpoint for Windows configuration instructions + registry file download [#392](https://github.com/juanfont/headscale/pull/392)
|
||||||
|
- Added embedded DERP server into Headscale [#388](https://github.com/juanfont/headscale/pull/388)
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
- Reduce the overhead of marshal/unmarshal for Hostinfo, routes and endpoints by using specific types in Machine [#371](https://github.com/juanfont/headscale/pull/371)
|
||||||
|
- Apply normalization function to FQDN on hostnames when hosts registers and retrieve informations [#363](https://github.com/juanfont/headscale/issues/363)
|
||||||
|
|
||||||
**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
|
||||||
@@ -29,17 +48,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
|
||||||
@@ -50,7 +69,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)
|
||||||
@@ -59,35 +78,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:
|
||||||
@@ -95,19 +114,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)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM docker.io/golang:1.17.7-bullseye AS build
|
FROM docker.io/golang:1.18.0-bullseye AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM docker.io/golang:1.17.7-alpine AS build
|
FROM docker.io/golang:1.18.0-alpine AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM docker.io/golang:1.17.7-bullseye AS build
|
FROM docker.io/golang:1.18.0-bullseye AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
@@ -7,5 +7,10 @@ RUN apt-get update \
|
|||||||
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \
|
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | apt-key add - \
|
||||||
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
|
&& curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | tee /etc/apt/sources.list.d/tailscale.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \
|
&& apt-get install -y ca-certificates tailscale=${TAILSCALE_VERSION} dnsutils \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ADD integration_test/etc_embedded_derp/tls/server.crt /usr/local/share/ca-certificates/
|
||||||
|
RUN chmod 644 /usr/local/share/ca-certificates/server.crt
|
||||||
|
|
||||||
|
RUN update-ca-certificates
|
||||||
|
3
Makefile
3
Makefile
@@ -23,6 +23,9 @@ test_integration:
|
|||||||
test_integration_cli:
|
test_integration_cli:
|
||||||
go test -tags integration -v integration_cli_test.go integration_common_test.go
|
go test -tags integration -v integration_cli_test.go integration_common_test.go
|
||||||
|
|
||||||
|
test_integration_derp:
|
||||||
|
go test -tags integration -v integration_embedded_derp_test.go integration_common_test.go
|
||||||
|
|
||||||
coverprofile_func:
|
coverprofile_func:
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
|
91
README.md
91
README.md
@@ -63,6 +63,7 @@ one of the maintainers.
|
|||||||
- Dual stack (IPv4 and IPv6)
|
- Dual stack (IPv4 and IPv6)
|
||||||
- Routing advertising (including exit nodes)
|
- Routing advertising (including exit nodes)
|
||||||
- Ephemeral nodes
|
- Ephemeral nodes
|
||||||
|
- Embedded [DERP server](https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp)
|
||||||
|
|
||||||
## Client OS support
|
## Client OS support
|
||||||
|
|
||||||
@@ -161,6 +162,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Juan Font</b></sub>
|
<sub style="font-size:14px"><b>Juan Font</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/restanrm>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/4344371?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Adrien Raffin-Caboisse/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Adrien Raffin-Caboisse</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/cure>
|
<a href=https://github.com/cure>
|
||||||
<img src=https://avatars.githubusercontent.com/u/149135?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Ward Vandewege/>
|
<img src=https://avatars.githubusercontent.com/u/149135?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Ward Vandewege/>
|
||||||
@@ -176,10 +184,19 @@ make build
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/restanrm>
|
<a href=https://github.com/e-zk>
|
||||||
<img src=https://avatars.githubusercontent.com/u/4344371?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Adrien Raffin-Caboisse/>
|
<img src=https://avatars.githubusercontent.com/u/58356365?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=e-zk/>
|
||||||
<br />
|
<br />
|
||||||
<sub style="font-size:14px"><b>Adrien Raffin-Caboisse</b></sub>
|
<sub style="font-size:14px"><b>e-zk</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/arch4ngel>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/11574161?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Justin Angel/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Justin Angel</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
@@ -189,8 +206,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Alessandro (Ale) Segala</b></sub>
|
<sub style="font-size:14px"><b>Alessandro (Ale) Segala</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/unreality>
|
<a href=https://github.com/unreality>
|
||||||
<img src=https://avatars.githubusercontent.com/u/352522?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=unreality/>
|
<img src=https://avatars.githubusercontent.com/u/352522?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=unreality/>
|
||||||
@@ -198,6 +213,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>unreality</b></sub>
|
<sub style="font-size:14px"><b>unreality</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/reynico>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/715768?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Nico/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Nico</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/negbie>
|
<a href=https://github.com/negbie>
|
||||||
<img src=https://avatars.githubusercontent.com/u/20154956?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Eugen Biegler/>
|
<img src=https://avatars.githubusercontent.com/u/20154956?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Eugen Biegler/>
|
||||||
@@ -212,6 +234,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Aaron Bieber</b></sub>
|
<sub style="font-size:14px"><b>Aaron Bieber</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/fdelucchijr>
|
<a href=https://github.com/fdelucchijr>
|
||||||
<img src=https://avatars.githubusercontent.com/u/69133647?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Fernando De Lucchi/>
|
<img src=https://avatars.githubusercontent.com/u/69133647?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Fernando De Lucchi/>
|
||||||
@@ -226,6 +250,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Hoàng Đức Hiếu</b></sub>
|
<sub style="font-size:14px"><b>Hoàng Đức Hiếu</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/mevansam>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/403630?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Mevan Samaratunga/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Mevan Samaratunga</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/dragetd>
|
<a href=https://github.com/dragetd>
|
||||||
<img src=https://avatars.githubusercontent.com/u/3639577?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Michael G./>
|
<img src=https://avatars.githubusercontent.com/u/3639577?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Michael G./>
|
||||||
@@ -233,8 +264,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Michael G.</b></sub>
|
<sub style="font-size:14px"><b>Michael G.</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/ptman>
|
<a href=https://github.com/ptman>
|
||||||
<img src=https://avatars.githubusercontent.com/u/24669?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Paul Tötterman/>
|
<img src=https://avatars.githubusercontent.com/u/24669?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Paul Tötterman/>
|
||||||
@@ -249,6 +278,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Casey Marshall</b></sub>
|
<sub style="font-size:14px"><b>Casey Marshall</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/SilverBut>
|
<a href=https://github.com/SilverBut>
|
||||||
<img src=https://avatars.githubusercontent.com/u/6560655?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Silver Bullet/>
|
<img src=https://avatars.githubusercontent.com/u/6560655?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Silver Bullet/>
|
||||||
@@ -264,10 +295,10 @@ make build
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/lachy-2849>
|
<a href=https://github.com/lachy2849>
|
||||||
<img src=https://avatars.githubusercontent.com/u/98844035?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=lachy-2849/>
|
<img src=https://avatars.githubusercontent.com/u/98844035?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=lachy2849/>
|
||||||
<br />
|
<br />
|
||||||
<sub style="font-size:14px"><b>lachy-2849</b></sub>
|
<sub style="font-size:14px"><b>lachy2849</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
@@ -277,8 +308,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>thomas</b></sub>
|
<sub style="font-size:14px"><b>thomas</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/aberoham>
|
<a href=https://github.com/aberoham>
|
||||||
<img src=https://avatars.githubusercontent.com/u/586805?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Abraham Ingersoll/>
|
<img src=https://avatars.githubusercontent.com/u/586805?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Abraham Ingersoll/>
|
||||||
@@ -293,6 +322,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Artem Klevtsov</b></sub>
|
<sub style="font-size:14px"><b>Artem Klevtsov</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/awoimbee>
|
<a href=https://github.com/awoimbee>
|
||||||
<img src=https://avatars.githubusercontent.com/u/22431493?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Arthur Woimbée/>
|
<img src=https://avatars.githubusercontent.com/u/22431493?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Arthur Woimbée/>
|
||||||
@@ -321,8 +352,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Felix Yan</b></sub>
|
<sub style="font-size:14px"><b>Felix Yan</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/JJGadgets>
|
<a href=https://github.com/JJGadgets>
|
||||||
<img src=https://avatars.githubusercontent.com/u/5709019?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=JJGadgets/>
|
<img src=https://avatars.githubusercontent.com/u/5709019?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=JJGadgets/>
|
||||||
@@ -337,6 +366,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Jamie Greeff</b></sub>
|
<sub style="font-size:14px"><b>Jamie Greeff</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/jimt>
|
<a href=https://github.com/jimt>
|
||||||
<img src=https://avatars.githubusercontent.com/u/180326?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Jim Tittsler/>
|
<img src=https://avatars.githubusercontent.com/u/180326?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Jim Tittsler/>
|
||||||
@@ -358,6 +389,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>rcursaru</b></sub>
|
<sub style="font-size:14px"><b>rcursaru</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/renovate-bot>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/25180681?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=WhiteSource Renovate/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>WhiteSource Renovate</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/ryanfowler>
|
<a href=https://github.com/ryanfowler>
|
||||||
<img src=https://avatars.githubusercontent.com/u/2668821?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Ryan Fowler/>
|
<img src=https://avatars.githubusercontent.com/u/2668821?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Ryan Fowler/>
|
||||||
@@ -365,8 +403,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Ryan Fowler</b></sub>
|
<sub style="font-size:14px"><b>Ryan Fowler</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/shaananc>
|
<a href=https://github.com/shaananc>
|
||||||
<img src=https://avatars.githubusercontent.com/u/2287839?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Shaanan Cohney/>
|
<img src=https://avatars.githubusercontent.com/u/2287839?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Shaanan Cohney/>
|
||||||
@@ -374,6 +410,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Shaanan Cohney</b></sub>
|
<sub style="font-size:14px"><b>Shaanan Cohney</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/m-tanner-dev0>
|
<a href=https://github.com/m-tanner-dev0>
|
||||||
<img src=https://avatars.githubusercontent.com/u/97977342?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Tanner/>
|
<img src=https://avatars.githubusercontent.com/u/97977342?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Tanner/>
|
||||||
@@ -409,6 +447,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Tjerk Woudsma</b></sub>
|
<sub style="font-size:14px"><b>Tjerk Woudsma</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/y0ngb1n>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/25719408?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Yang Bin/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Yang Bin</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
@@ -425,6 +470,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>ZiYuan</b></sub>
|
<sub style="font-size:14px"><b>ZiYuan</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/bravechamp>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/48980452?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=bravechamp/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>bravechamp</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/derelm>
|
<a href=https://github.com/derelm>
|
||||||
<img src=https://avatars.githubusercontent.com/u/465155?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=derelm/>
|
<img src=https://avatars.githubusercontent.com/u/465155?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=derelm/>
|
||||||
@@ -432,13 +484,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>derelm</b></sub>
|
<sub style="font-size:14px"><b>derelm</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
|
||||||
<a href=https://github.com/e-zk>
|
|
||||||
<img src=https://avatars.githubusercontent.com/u/58356365?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=e-zk/>
|
|
||||||
<br />
|
|
||||||
<sub style="font-size:14px"><b>e-zk</b></sub>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/ignoramous>
|
<a href=https://github.com/ignoramous>
|
||||||
<img src=https://avatars.githubusercontent.com/u/852289?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=ignoramous/>
|
<img src=https://avatars.githubusercontent.com/u/852289?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=ignoramous/>
|
||||||
|
104
acls.go
104
acls.go
@@ -5,11 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@@ -17,7 +19,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
errEmptyPolicy = Error("empty policy")
|
errEmptyPolicy = Error("empty policy")
|
||||||
errInvalidAction = Error("invalid action")
|
errInvalidAction = Error("invalid action")
|
||||||
errInvalidUserSection = Error("invalid user section")
|
|
||||||
errInvalidGroup = Error("invalid group")
|
errInvalidGroup = Error("invalid group")
|
||||||
errInvalidTag = Error("invalid tag")
|
errInvalidTag = Error("invalid tag")
|
||||||
errInvalidPortFormat = Error("invalid port format")
|
errInvalidPortFormat = Error("invalid port format")
|
||||||
@@ -53,16 +54,36 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch filepath.Ext(path) {
|
||||||
|
case ".yml", ".yaml":
|
||||||
|
log.Debug().
|
||||||
|
Str("path", path).
|
||||||
|
Bytes("file", policyBytes).
|
||||||
|
Msg("Loading ACLs from YAML")
|
||||||
|
|
||||||
|
err := yaml.Unmarshal(policyBytes, &policy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Interface("policy", policy).
|
||||||
|
Msg("Loaded policy from YAML")
|
||||||
|
|
||||||
|
default:
|
||||||
ast, err := hujson.Parse(policyBytes)
|
ast, err := hujson.Parse(policyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Standardize()
|
ast.Standardize()
|
||||||
policyBytes = ast.Pack()
|
policyBytes = ast.Pack()
|
||||||
err = json.Unmarshal(policyBytes, &policy)
|
err = json.Unmarshal(policyBytes, &policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if policy.IsZero() {
|
if policy.IsZero() {
|
||||||
return errEmptyPolicy
|
return errEmptyPolicy
|
||||||
}
|
}
|
||||||
@@ -138,7 +159,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(
|
||||||
@@ -164,7 +185,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
|
||||||
}
|
}
|
||||||
@@ -196,14 +222,19 @@ 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 == "*" {
|
||||||
return []string{"*"}, nil
|
return []string{"*"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("alias", alias).
|
||||||
|
Msg("Expanding")
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -218,20 +249,14 @@ 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
|
||||||
}
|
}
|
||||||
for _, namespace := range owners {
|
for _, namespace := range owners {
|
||||||
machines := filterMachinesByNamespace(machines, namespace)
|
machines := filterMachinesByNamespace(machines, namespace)
|
||||||
for _, machine := range machines {
|
for _, machine := range machines {
|
||||||
if len(machine.HostInfo) == 0 {
|
hi := machine.GetHostInfo()
|
||||||
continue
|
|
||||||
}
|
|
||||||
hi, err := machine.GetHostInfo()
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, t := range hi.RequestTags {
|
for _, t := range hi.RequestTags {
|
||||||
if alias == t {
|
if alias == t {
|
||||||
ips = append(ips, machine.IPAddresses.ToStringSlice()...)
|
ips = append(ips, machine.IPAddresses.ToStringSlice()...)
|
||||||
@@ -245,10 +270,8 @@ func expandAlias(
|
|||||||
|
|
||||||
// if alias is a namespace
|
// if alias is a namespace
|
||||||
nodes := filterMachinesByNamespace(machines, alias)
|
nodes := filterMachinesByNamespace(machines, alias)
|
||||||
nodes, err := excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
|
nodes = excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, n := range nodes {
|
for _, n := range nodes {
|
||||||
ips = append(ips, n.IPAddresses.ToStringSlice()...)
|
ips = append(ips, n.IPAddresses.ToStringSlice()...)
|
||||||
}
|
}
|
||||||
@@ -273,7 +296,9 @@ func expandAlias(
|
|||||||
return []string{cidr.String()}, nil
|
return []string{cidr.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ips, errInvalidUserSection
|
log.Warn().Msgf("No IPs found with the alias %v", alias)
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones
|
// excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones
|
||||||
@@ -283,7 +308,7 @@ func excludeCorrectlyTaggedNodes(
|
|||||||
aclPolicy ACLPolicy,
|
aclPolicy ACLPolicy,
|
||||||
nodes []Machine,
|
nodes []Machine,
|
||||||
namespace string,
|
namespace string,
|
||||||
) ([]Machine, error) {
|
) []Machine {
|
||||||
out := []Machine{}
|
out := []Machine{}
|
||||||
tags := []string{}
|
tags := []string{}
|
||||||
for tag, ns := range aclPolicy.TagOwners {
|
for tag, ns := range aclPolicy.TagOwners {
|
||||||
@@ -293,15 +318,8 @@ func excludeCorrectlyTaggedNodes(
|
|||||||
}
|
}
|
||||||
// for each machine if tag is in tags list, don't append it.
|
// for each machine if tag is in tags list, don't append it.
|
||||||
for _, machine := range nodes {
|
for _, machine := range nodes {
|
||||||
if len(machine.HostInfo) == 0 {
|
hi := machine.GetHostInfo()
|
||||||
out = append(out, machine)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hi, err := machine.GetHostInfo()
|
|
||||||
if err != nil {
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
found := false
|
found := false
|
||||||
for _, t := range hi.RequestTags {
|
for _, t := range hi.RequestTags {
|
||||||
if containsString(tags, t) {
|
if containsString(tags, t) {
|
||||||
@@ -315,7 +333,7 @@ func excludeCorrectlyTaggedNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
||||||
@@ -374,7 +392,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 {
|
||||||
@@ -386,7 +408,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
|
||||||
}
|
}
|
||||||
@@ -401,8 +423,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",
|
||||||
@@ -410,14 +437,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 := NormalizeToFQDNRules(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
|
||||||
}
|
}
|
||||||
|
235
acls_test.go
235
acls_test.go
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
"gorm.io/datatypes"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@@ -108,9 +107,12 @@ func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
|
|||||||
|
|
||||||
_, err = app.GetMachine("user1", "testmachine")
|
_, err = app.GetMachine("user1", "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
hostInfo := []byte(
|
hostInfo := tailcfg.Hostinfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
|
OS: "centos",
|
||||||
)
|
Hostname: "testmachine",
|
||||||
|
RequestTags: []string{"tag:test"},
|
||||||
|
}
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: "foo",
|
MachineKey: "foo",
|
||||||
@@ -119,10 +121,9 @@ 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: datatypes.JSON(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
@@ -152,9 +153,12 @@ func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
|
|||||||
|
|
||||||
_, err = app.GetMachine("user1", "testmachine")
|
_, err = app.GetMachine("user1", "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
hostInfo := []byte(
|
hostInfo := tailcfg.Hostinfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
|
OS: "centos",
|
||||||
)
|
Hostname: "testmachine",
|
||||||
|
RequestTags: []string{"tag:test"},
|
||||||
|
}
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
MachineKey: "12345",
|
MachineKey: "12345",
|
||||||
@@ -163,10 +167,9 @@ 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: datatypes.JSON(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
@@ -196,9 +199,12 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
|
|||||||
|
|
||||||
_, err = app.GetMachine("user1", "testmachine")
|
_, err = app.GetMachine("user1", "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
hostInfo := []byte(
|
hostInfo := tailcfg.Hostinfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:foo\"]}",
|
OS: "centos",
|
||||||
)
|
Hostname: "testmachine",
|
||||||
|
RequestTags: []string{"tag:foo"},
|
||||||
|
}
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
MachineKey: "12345",
|
MachineKey: "12345",
|
||||||
@@ -207,10 +213,9 @@ 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: datatypes.JSON(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
@@ -239,9 +244,12 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
|
|||||||
|
|
||||||
_, err = app.GetMachine("user1", "webserver")
|
_, err = app.GetMachine("user1", "webserver")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
hostInfo := []byte(
|
hostInfo := tailcfg.Hostinfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"webserver\",\"RequestTags\":[\"tag:webapp\"]}",
|
OS: "centos",
|
||||||
)
|
Hostname: "webserver",
|
||||||
|
RequestTags: []string{"tag:webapp"},
|
||||||
|
}
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
MachineKey: "12345",
|
MachineKey: "12345",
|
||||||
@@ -250,14 +258,16 @@ 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: datatypes.JSON(hostInfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
_, err = app.GetMachine("user1", "user")
|
_, err = app.GetMachine("user1", "user")
|
||||||
hostInfo = []byte("{\"OS\":\"debian\",\"Hostname\":\"user\"}")
|
hostInfo2 := tailcfg.Hostinfo{
|
||||||
|
OS: "debian",
|
||||||
|
Hostname: "Hostname",
|
||||||
|
}
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
machine = Machine{
|
machine = Machine{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
@@ -267,10 +277,9 @@ 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: datatypes.JSON(hostInfo),
|
HostInfo: HostInfo(hostInfo2),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
@@ -328,6 +337,22 @@ func (s *Suite) TestPortWildcard(c *check.C) {
|
|||||||
c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
|
c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Suite) TestPortWildcardYAML(c *check.C) {
|
||||||
|
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.yaml")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
rules, err := app.generateACLRules()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(rules, check.NotNil)
|
||||||
|
|
||||||
|
c.Assert(rules, check.HasLen, 1)
|
||||||
|
c.Assert(rules[0].DstPorts, check.HasLen, 1)
|
||||||
|
c.Assert(rules[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
||||||
|
c.Assert(rules[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
||||||
|
c.Assert(rules[0].SrcIPs, check.HasLen, 1)
|
||||||
|
c.Assert(rules[0].SrcIPs[0], check.Equals, "*")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Suite) TestPortNamespace(c *check.C) {
|
func (s *Suite) TestPortNamespace(c *check.C) {
|
||||||
namespace, err := app.CreateNamespace("testnamespace")
|
namespace, err := app.CreateNamespace("testnamespace")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -345,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),
|
||||||
@@ -388,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),
|
||||||
@@ -416,6 +439,7 @@ 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
|
||||||
@@ -433,6 +457,7 @@ func Test_expandGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
group: "group:test",
|
group: "group:test",
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"user1", "user2", "user3"},
|
want: []string{"user1", "user2", "user3"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -447,14 +472,53 @@ func Test_expandGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
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)
|
||||||
|
|
||||||
@@ -471,6 +535,7 @@ 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
|
||||||
@@ -485,6 +550,7 @@ func Test_expandTagOwners(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -497,6 +563,7 @@ func Test_expandTagOwners(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -509,6 +576,7 @@ func Test_expandTagOwners(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -520,6 +588,7 @@ func Test_expandTagOwners(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -532,6 +601,7 @@ func Test_expandTagOwners(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -539,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)
|
||||||
|
|
||||||
@@ -704,6 +778,7 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
machines []Machine
|
machines []Machine
|
||||||
aclPolicy ACLPolicy
|
aclPolicy ACLPolicy
|
||||||
alias string
|
alias string
|
||||||
|
stripEmailDomain bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -724,6 +799,7 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
aclPolicy: ACLPolicy{},
|
aclPolicy: ACLPolicy{},
|
||||||
|
stripEmailDomain: true,
|
||||||
},
|
},
|
||||||
want: []string{"*"},
|
want: []string{"*"},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -761,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,
|
||||||
@@ -798,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,
|
||||||
@@ -808,6 +886,7 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -822,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,
|
||||||
@@ -832,6 +912,7 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -842,6 +923,7 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
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,
|
||||||
@@ -856,18 +938,22 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
netaddr.MustParseIP("100.64.0.1"),
|
netaddr.MustParseIP("100.64.0.1"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
netaddr.MustParseIP("100.64.0.2"),
|
netaddr.MustParseIP("100.64.0.2"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
@@ -885,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,
|
||||||
@@ -925,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,
|
||||||
@@ -939,18 +1027,22 @@ func Test_expandAlias(t *testing.T) {
|
|||||||
netaddr.MustParseIP("100.64.0.1"),
|
netaddr.MustParseIP("100.64.0.1"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:accountant-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
netaddr.MustParseIP("100.64.0.2"),
|
netaddr.MustParseIP("100.64.0.2"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:accountant-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
@@ -968,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,
|
||||||
@@ -979,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)
|
||||||
@@ -1016,18 +1110,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
netaddr.MustParseIP("100.64.0.1"),
|
netaddr.MustParseIP("100.64.0.1"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:accountant-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
netaddr.MustParseIP("100.64.0.2"),
|
netaddr.MustParseIP("100.64.0.2"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "foo",
|
||||||
|
RequestTags: []string{"tag:accountant-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
@@ -1044,7 +1142,6 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "all nodes have invalid tags, don't exclude them",
|
name: "all nodes have invalid tags, don't exclude them",
|
||||||
@@ -1058,18 +1155,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
netaddr.MustParseIP("100.64.0.1"),
|
netaddr.MustParseIP("100.64.0.1"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "hr-web1",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
netaddr.MustParseIP("100.64.0.2"),
|
netaddr.MustParseIP("100.64.0.2"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "hr-web2",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
@@ -1086,18 +1187,22 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
netaddr.MustParseIP("100.64.0.1"),
|
netaddr.MustParseIP("100.64.0.1"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "hr-web1",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
netaddr.MustParseIP("100.64.0.2"),
|
netaddr.MustParseIP("100.64.0.2"),
|
||||||
},
|
},
|
||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
HostInfo: []byte(
|
HostInfo: HostInfo{
|
||||||
"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
|
OS: "centos",
|
||||||
),
|
Hostname: "hr-web2",
|
||||||
|
RequestTags: []string{"tag:hr-webserver"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IPAddresses: MachineAddresses{
|
IPAddresses: MachineAddresses{
|
||||||
@@ -1106,25 +1211,15 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
|||||||
Namespace: Namespace{Name: "joe"},
|
Namespace: Namespace{Name: "joe"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
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 := excludeCorrectlyTaggedNodes(
|
got := excludeCorrectlyTaggedNodes(
|
||||||
test.args.aclPolicy,
|
test.args.aclPolicy,
|
||||||
test.args.nodes,
|
test.args.nodes,
|
||||||
test.args.namespace,
|
test.args.namespace,
|
||||||
)
|
)
|
||||||
if (err != nil) != test.wantErr {
|
|
||||||
t.Errorf(
|
|
||||||
"excludeCorrectlyTaggedNodes() error = %v, wantErr %v",
|
|
||||||
err,
|
|
||||||
test.wantErr,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, test.want) {
|
if !reflect.DeepEqual(got, test.want) {
|
||||||
t.Errorf("excludeCorrectlyTaggedNodes() = %v, want %v", got, test.want)
|
t.Errorf("excludeCorrectlyTaggedNodes() = %v, want %v", got, test.want)
|
||||||
}
|
}
|
||||||
|
@@ -5,23 +5,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ACLPolicy represents a Tailscale ACL Policy.
|
// ACLPolicy represents a Tailscale ACL Policy.
|
||||||
type ACLPolicy struct {
|
type ACLPolicy struct {
|
||||||
Groups Groups `json:"Groups"`
|
Groups Groups `json:"Groups" yaml:"Groups"`
|
||||||
Hosts Hosts `json:"Hosts"`
|
Hosts Hosts `json:"Hosts" yaml:"Hosts"`
|
||||||
TagOwners TagOwners `json:"TagOwners"`
|
TagOwners TagOwners `json:"TagOwners" yaml:"TagOwners"`
|
||||||
ACLs []ACL `json:"ACLs"`
|
ACLs []ACL `json:"ACLs" yaml:"ACLs"`
|
||||||
Tests []ACLTest `json:"Tests"`
|
Tests []ACLTest `json:"Tests" yaml:"Tests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL is a basic rule for the ACL Policy.
|
// ACL is a basic rule for the ACL Policy.
|
||||||
type ACL struct {
|
type ACL struct {
|
||||||
Action string `json:"Action"`
|
Action string `json:"Action" yaml:"Action"`
|
||||||
Users []string `json:"Users"`
|
Users []string `json:"Users" yaml:"Users"`
|
||||||
Ports []string `json:"Ports"`
|
Ports []string `json:"Ports" yaml:"Ports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Groups references a series of alias in the ACL rules.
|
// Groups references a series of alias in the ACL rules.
|
||||||
@@ -35,9 +36,9 @@ type TagOwners map[string][]string
|
|||||||
|
|
||||||
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
|
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
|
||||||
type ACLTest struct {
|
type ACLTest struct {
|
||||||
User string `json:"User"`
|
User string `json:"User" yaml:"User"`
|
||||||
Allow []string `json:"Allow"`
|
Allow []string `json:"Allow" yaml:"Allow"`
|
||||||
Deny []string `json:"Deny,omitempty"`
|
Deny []string `json:"Deny,omitempty" yaml:"Deny,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
|
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
|
||||||
@@ -69,6 +70,27 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML allows to parse the Hosts directly into netaddr objects.
|
||||||
|
func (hosts *Hosts) UnmarshalYAML(data []byte) error {
|
||||||
|
newHosts := Hosts{}
|
||||||
|
hostIPPrefixMap := make(map[string]string)
|
||||||
|
|
||||||
|
err := yaml.Unmarshal(data, &hostIPPrefixMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for host, prefixStr := range hostIPPrefixMap {
|
||||||
|
prefix, err := netaddr.ParseIPPrefix(prefixStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newHosts[host] = prefix
|
||||||
|
}
|
||||||
|
*hosts = newHosts
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsZero is perhaps a bit naive here.
|
// IsZero is perhaps a bit naive here.
|
||||||
func (policy ACLPolicy) IsZero() bool {
|
func (policy ACLPolicy) IsZero() bool {
|
||||||
if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
|
if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
|
||||||
|
220
api.go
220
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(
|
||||||
@@ -45,22 +45,21 @@ type registerWebAPITemplateConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var registerWebAPITemplate = template.Must(
|
var registerWebAPITemplate = template.Must(
|
||||||
template.New("registerweb").Parse(`<html>
|
template.New("registerweb").Parse(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Registration - Headscale</title>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>headscale</h1>
|
<h1>headscale</h1>
|
||||||
|
<h2>Machine registration</h2>
|
||||||
<p>
|
<p>
|
||||||
Run the command below in the headscale server to add this machine to your network:
|
Run the command below in the headscale server to add this machine to your network:
|
||||||
</p>
|
</p>
|
||||||
|
<pre><code>headscale -n NAMESPACE nodes register --key {{.Key}}</code></pre>
|
||||||
<p>
|
|
||||||
<code>
|
|
||||||
<b>headscale -n NAMESPACE nodes register --key {{.Key}}</b>
|
|
||||||
</code>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>`),
|
</html>
|
||||||
)
|
`))
|
||||||
|
|
||||||
// RegisterWebAPI shows a simple message in the browser to point to the CLI
|
// RegisterWebAPI shows a simple message in the browser to point to the CLI
|
||||||
// Listens in /register.
|
// Listens in /register.
|
||||||
@@ -125,25 +124,63 @@ 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
|
hname, err := NormalizeToFQDNRules(
|
||||||
|
req.Hostinfo.Hostname,
|
||||||
|
h.cfg.OIDC.StripEmaildomain,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("func", "RegistrationHandler").
|
||||||
|
Str("hostinfo.name", req.Hostinfo.Hostname).
|
||||||
|
Err(err)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if machine.Registered {
|
// 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: hname,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +217,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 +430,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 +493,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 +511,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 +529,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 +552,87 @@ 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).
|
|
||||||
|
if pak != nil {
|
||||||
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
|
} else {
|
||||||
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error").Inc()
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if machine.isRegistered() {
|
log.Debug().
|
||||||
|
Str("func", "handleAuthKey").
|
||||||
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
|
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||||
|
|
||||||
|
nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
|
|
||||||
|
// retrieve machine information if it exist
|
||||||
|
// The error is not important, because if it does not
|
||||||
|
// exist, then this is a new machine and we will move
|
||||||
|
// on to registration.
|
||||||
|
machine, _ := h.GetMachineByMachineKey(machineKey)
|
||||||
|
if machine != nil {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("machine already registered, reauthenticating")
|
Msg("machine already registered, refreshing with new auth key")
|
||||||
|
|
||||||
h.RefreshMachine(&machine, registerRequest.Expiry)
|
machine.NodeKey = nodeKey
|
||||||
|
machine.AuthKeyID = uint(pak.ID)
|
||||||
|
h.RefreshMachine(machine, registerRequest.Expiry)
|
||||||
} else {
|
} else {
|
||||||
log.Debug().
|
now := time.Now().UTC()
|
||||||
Str("func", "handleAuthKey").
|
machineToRegister := Machine{
|
||||||
Str("machine", machine.Name).
|
Name: registerRequest.Hostinfo.Hostname,
|
||||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
NamespaceID: pak.Namespace.ID,
|
||||||
|
MachineKey: machineKeyStr,
|
||||||
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
|
Expiry: ®isterRequest.Expiry,
|
||||||
|
NodeKey: nodeKey,
|
||||||
|
LastSeen: &now,
|
||||||
|
AuthKeyID: uint(pak.ID),
|
||||||
|
}
|
||||||
|
|
||||||
h.ipAllocationMutex.Lock()
|
machine, err = h.RegisterMachine(
|
||||||
|
machineToRegister,
|
||||||
ips, err := h.getAvailableIPs()
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Err(err).
|
||||||
Str("machine", machine.Name).
|
Msg("could not register machine")
|
||||||
Msg("Failed to find an available IP address")
|
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
|
||||||
Inc()
|
Inc()
|
||||||
|
ctx.String(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"could not register machine",
|
||||||
|
)
|
||||||
|
|
||||||
return
|
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
|
h.UsePreAuthKey(pak)
|
||||||
h.db.Save(&pak)
|
|
||||||
|
|
||||||
resp.MachineAuthorized = true
|
resp.MachineAuthorized = true
|
||||||
resp.User = *pak.Namespace.toUser()
|
resp.User = *pak.Namespace.toUser()
|
||||||
@@ -619,21 +641,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")
|
||||||
}
|
}
|
||||||
|
99
app.go
99
app.go
@@ -47,6 +47,14 @@ import (
|
|||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errSTUNAddressNotSet = Error("STUN address not set")
|
||||||
|
errUnsupportedDatabase = Error("unsupported DB")
|
||||||
|
errUnsupportedLetsEncryptChallengeType = Error(
|
||||||
|
"unknown value for Lets Encrypt challenge type",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthPrefix = "Bearer "
|
AuthPrefix = "Bearer "
|
||||||
Postgres = "postgres"
|
Postgres = "postgres"
|
||||||
@@ -55,13 +63,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")
|
|
||||||
errUnsupportedLetsEncryptChallengeType = Error(
|
|
||||||
"unknown value for Lets Encrypt challenge type",
|
|
||||||
)
|
|
||||||
|
|
||||||
DisabledClientAuth = "disabled"
|
DisabledClientAuth = "disabled"
|
||||||
RelaxedClientAuth = "relaxed"
|
RelaxedClientAuth = "relaxed"
|
||||||
@@ -72,6 +75,7 @@ const (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
ServerURL string
|
ServerURL string
|
||||||
Addr string
|
Addr string
|
||||||
|
MetricsAddr string
|
||||||
GRPCAddr string
|
GRPCAddr string
|
||||||
GRPCAllowInsecure bool
|
GRPCAllowInsecure bool
|
||||||
EphemeralNodeInactivityTimeout time.Duration
|
EphemeralNodeInactivityTimeout time.Duration
|
||||||
@@ -119,6 +123,11 @@ type OIDCConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DERPConfig struct {
|
type DERPConfig struct {
|
||||||
|
ServerEnabled bool
|
||||||
|
ServerRegionID int
|
||||||
|
ServerRegionCode string
|
||||||
|
ServerRegionName string
|
||||||
|
STUNAddr string
|
||||||
URLs []url.URL
|
URLs []url.URL
|
||||||
Paths []string
|
Paths []string
|
||||||
AutoUpdate bool
|
AutoUpdate bool
|
||||||
@@ -142,6 +151,7 @@ type Headscale struct {
|
|||||||
privateKey *key.MachinePrivate
|
privateKey *key.MachinePrivate
|
||||||
|
|
||||||
DERPMap *tailcfg.DERPMap
|
DERPMap *tailcfg.DERPMap
|
||||||
|
DERPServer *DERPServer
|
||||||
|
|
||||||
aclPolicy *ACLPolicy
|
aclPolicy *ACLPolicy
|
||||||
aclRules []tailcfg.FilterRule
|
aclRules []tailcfg.FilterRule
|
||||||
@@ -150,9 +160,8 @@ type Headscale struct {
|
|||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -178,7 +187,6 @@ func LookupTLSClientAuthMode(mode string) (tls.ClientAuthType, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeadscale returns the Headscale app.
|
|
||||||
func NewHeadscale(cfg Config) (*Headscale, error) {
|
func NewHeadscale(cfg Config) (*Headscale, error) {
|
||||||
privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
|
privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,9 +210,9 @@ 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{
|
||||||
@@ -213,7 +221,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
|||||||
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()
|
||||||
@@ -239,6 +247,14 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.DERP.ServerEnabled {
|
||||||
|
embeddedDERPServer, err := app.NewDERPServer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
app.DERPServer = embeddedDERPServer
|
||||||
|
}
|
||||||
|
|
||||||
return &app, nil
|
return &app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,8 +411,6 @@ func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.AbortWithStatus(http.StatusUnauthorized)
|
|
||||||
|
|
||||||
valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
@@ -434,11 +448,17 @@ func (h *Headscale) ensureUnixSocketIsAbsent() error {
|
|||||||
return os.Remove(h.cfg.UnixSocket)
|
return os.Remove(h.cfg.UnixSocket)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine {
|
func (h *Headscale) createPrometheusRouter() *gin.Engine {
|
||||||
router := gin.Default()
|
promRouter := gin.Default()
|
||||||
|
|
||||||
prometheus := ginprometheus.NewPrometheus("gin")
|
prometheus := ginprometheus.NewPrometheus("gin")
|
||||||
prometheus.Use(router)
|
prometheus.Use(promRouter)
|
||||||
|
|
||||||
|
return promRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
router.GET(
|
router.GET(
|
||||||
"/health",
|
"/health",
|
||||||
@@ -450,11 +470,19 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine {
|
|||||||
router.POST("/machine/:id", h.RegistrationHandler)
|
router.POST("/machine/:id", h.RegistrationHandler)
|
||||||
router.GET("/oidc/register/:mkey", h.RegisterOIDC)
|
router.GET("/oidc/register/:mkey", h.RegisterOIDC)
|
||||||
router.GET("/oidc/callback", h.OIDCCallback)
|
router.GET("/oidc/callback", h.OIDCCallback)
|
||||||
router.GET("/apple", h.AppleMobileConfig)
|
router.GET("/apple", h.AppleConfigMessage)
|
||||||
router.GET("/apple/:platform", h.ApplePlatformConfig)
|
router.GET("/apple/:platform", h.ApplePlatformConfig)
|
||||||
|
router.GET("/windows", h.WindowsConfigMessage)
|
||||||
|
router.GET("/windows/tailscale.reg", h.WindowsRegConfig)
|
||||||
router.GET("/swagger", SwaggerUI)
|
router.GET("/swagger", SwaggerUI)
|
||||||
router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1)
|
router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1)
|
||||||
|
|
||||||
|
if h.cfg.DERP.ServerEnabled {
|
||||||
|
router.Any("/derp", h.DERPHandler)
|
||||||
|
router.Any("/derp/probe", h.DERPProbeHandler)
|
||||||
|
router.Any("/bootstrap-dns", h.DERPBootstrapDNSHandler)
|
||||||
|
}
|
||||||
|
|
||||||
api := router.Group("/api")
|
api := router.Group("/api")
|
||||||
api.Use(h.httpAuthenticationMiddleware)
|
api.Use(h.httpAuthenticationMiddleware)
|
||||||
{
|
{
|
||||||
@@ -473,6 +501,16 @@ func (h *Headscale) Serve() error {
|
|||||||
// Fetch an initial DERP Map before we start serving
|
// Fetch an initial DERP Map before we start serving
|
||||||
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
||||||
|
|
||||||
|
if h.cfg.DERP.ServerEnabled {
|
||||||
|
// When embedded DERP is enabled we always need a STUN server
|
||||||
|
if h.cfg.DERP.STUNAddr == "" {
|
||||||
|
return errSTUNAddressNotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
|
||||||
|
go h.ServeSTUN()
|
||||||
|
}
|
||||||
|
|
||||||
if h.cfg.DERP.AutoUpdate {
|
if h.cfg.DERP.AutoUpdate {
|
||||||
derpMapCancelChannel := make(chan struct{})
|
derpMapCancelChannel := make(chan struct{})
|
||||||
defer func() { derpMapCancelChannel <- struct{}{} }()
|
defer func() { derpMapCancelChannel <- struct{}{} }()
|
||||||
@@ -650,6 +688,27 @@ func (h *Headscale) Serve() error {
|
|||||||
log.Info().
|
log.Info().
|
||||||
Msgf("listening and serving HTTP on: %s", h.cfg.Addr)
|
Msgf("listening and serving HTTP on: %s", h.cfg.Addr)
|
||||||
|
|
||||||
|
promRouter := h.createPrometheusRouter()
|
||||||
|
|
||||||
|
promHTTPServer := &http.Server{
|
||||||
|
Addr: h.cfg.MetricsAddr,
|
||||||
|
Handler: promRouter,
|
||||||
|
ReadTimeout: HTTPReadTimeout,
|
||||||
|
WriteTimeout: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
var promHTTPListener net.Listener
|
||||||
|
promHTTPListener, err = net.Listen("tcp", h.cfg.MetricsAddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorGroup.Go(func() error { return promHTTPServer.Serve(promHTTPListener) })
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Msgf("listening and serving metrics on: %s", h.cfg.MetricsAddr)
|
||||||
|
|
||||||
return errorGroup.Wait()
|
return errorGroup.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
41
cli_test.go
41
cli_test.go
@@ -1,41 +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)
|
|
||||||
|
|
||||||
_, err = machineAfterRegistering.GetHostInfo()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
@@ -38,11 +38,13 @@ func init() {
|
|||||||
var apiKeysCmd = &cobra.Command{
|
var apiKeysCmd = &cobra.Command{
|
||||||
Use: "apikeys",
|
Use: "apikeys",
|
||||||
Short: "Handle the Api keys in Headscale",
|
Short: "Handle the Api keys in Headscale",
|
||||||
|
Aliases: []string{"apikey", "api"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listAPIKeys = &cobra.Command{
|
var listAPIKeys = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List the Api keys for headscale",
|
Short: "List the Api keys for headscale",
|
||||||
|
Aliases: []string{"ls", "show"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -107,6 +109,7 @@ var createAPIKeyCmd = &cobra.Command{
|
|||||||
Creates a new Api key, the Api key is only visible on creation
|
Creates a new Api key, the Api key is only visible on creation
|
||||||
and cannot be retrieved again.
|
and cannot be retrieved again.
|
||||||
If you loose a key, create a new one and revoke (expire) the old one.`,
|
If you loose a key, create a new one and revoke (expire) the old one.`,
|
||||||
|
Aliases: []string{"c", "new"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
|
|||||||
var expireAPIKeyCmd = &cobra.Command{
|
var expireAPIKeyCmd = &cobra.Command{
|
||||||
Use: "expire",
|
Use: "expire",
|
||||||
Short: "Expire an ApiKey",
|
Short: "Expire an ApiKey",
|
||||||
Aliases: []string{"revoke"},
|
Aliases: []string{"revoke", "exp", "e"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ func init() {
|
|||||||
var generateCmd = &cobra.Command{
|
var generateCmd = &cobra.Command{
|
||||||
Use: "generate",
|
Use: "generate",
|
||||||
Short: "Generate commands",
|
Short: "Generate commands",
|
||||||
|
Aliases: []string{"gen"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var generatePrivateKeyCmd = &cobra.Command{
|
var generatePrivateKeyCmd = &cobra.Command{
|
||||||
|
@@ -27,11 +27,13 @@ const (
|
|||||||
var namespaceCmd = &cobra.Command{
|
var namespaceCmd = &cobra.Command{
|
||||||
Use: "namespaces",
|
Use: "namespaces",
|
||||||
Short: "Manage the namespaces of Headscale",
|
Short: "Manage the namespaces of Headscale",
|
||||||
|
Aliases: []string{"namespace", "ns", "user", "users"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var createNamespaceCmd = &cobra.Command{
|
var createNamespaceCmd = &cobra.Command{
|
||||||
Use: "create NAME",
|
Use: "create NAME",
|
||||||
Short: "Creates a new namespace",
|
Short: "Creates a new namespace",
|
||||||
|
Aliases: []string{"c", "new"},
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errMissingParameter
|
return errMissingParameter
|
||||||
@@ -74,6 +76,7 @@ var createNamespaceCmd = &cobra.Command{
|
|||||||
var destroyNamespaceCmd = &cobra.Command{
|
var destroyNamespaceCmd = &cobra.Command{
|
||||||
Use: "destroy NAME",
|
Use: "destroy NAME",
|
||||||
Short: "Destroys a namespace",
|
Short: "Destroys a namespace",
|
||||||
|
Aliases: []string{"delete"},
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errMissingParameter
|
return errMissingParameter
|
||||||
@@ -146,6 +149,7 @@ var destroyNamespaceCmd = &cobra.Command{
|
|||||||
var listNamespacesCmd = &cobra.Command{
|
var listNamespacesCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all the namespaces",
|
Short: "List all the namespaces",
|
||||||
|
Aliases: []string{"ls", "show"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -199,6 +203,7 @@ var listNamespacesCmd = &cobra.Command{
|
|||||||
var renameNamespaceCmd = &cobra.Command{
|
var renameNamespaceCmd = &cobra.Command{
|
||||||
Use: "rename OLD_NAME NEW_NAME",
|
Use: "rename OLD_NAME NEW_NAME",
|
||||||
Short: "Renames a namespace",
|
Short: "Renames a namespace",
|
||||||
|
Aliases: []string{"mv"},
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
expectedArguments := 2
|
expectedArguments := 2
|
||||||
if len(args) < expectedArguments {
|
if len(args) < expectedArguments {
|
||||||
|
@@ -51,6 +51,7 @@ func init() {
|
|||||||
var nodeCmd = &cobra.Command{
|
var nodeCmd = &cobra.Command{
|
||||||
Use: "nodes",
|
Use: "nodes",
|
||||||
Short: "Manage the nodes of Headscale",
|
Short: "Manage the nodes of Headscale",
|
||||||
|
Aliases: []string{"node", "machine", "machines"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var registerNodeCmd = &cobra.Command{
|
var registerNodeCmd = &cobra.Command{
|
||||||
@@ -106,6 +107,7 @@ var registerNodeCmd = &cobra.Command{
|
|||||||
var listNodesCmd = &cobra.Command{
|
var listNodesCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List nodes",
|
Short: "List nodes",
|
||||||
|
Aliases: []string{"ls", "show"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
namespace, err := cmd.Flags().GetString("namespace")
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
@@ -164,7 +166,7 @@ var expireNodeCmd = &cobra.Command{
|
|||||||
Use: "expire",
|
Use: "expire",
|
||||||
Short: "Expire (log out) a machine in your network",
|
Short: "Expire (log out) a machine in your network",
|
||||||
Long: "Expiring a node will keep the node in the database and force it to reauthenticate.",
|
Long: "Expiring a node will keep the node in the database and force it to reauthenticate.",
|
||||||
Aliases: []string{"logout"},
|
Aliases: []string{"logout", "exp", "e"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -208,6 +210,7 @@ var expireNodeCmd = &cobra.Command{
|
|||||||
var deleteNodeCmd = &cobra.Command{
|
var deleteNodeCmd = &cobra.Command{
|
||||||
Use: "delete",
|
Use: "delete",
|
||||||
Short: "Delete a node",
|
Short: "Delete a node",
|
||||||
|
Aliases: []string{"del"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
@@ -37,11 +37,13 @@ func init() {
|
|||||||
var preauthkeysCmd = &cobra.Command{
|
var preauthkeysCmd = &cobra.Command{
|
||||||
Use: "preauthkeys",
|
Use: "preauthkeys",
|
||||||
Short: "Handle the preauthkeys in Headscale",
|
Short: "Handle the preauthkeys in Headscale",
|
||||||
|
Aliases: []string{"preauthkey", "authkey", "pre"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listPreAuthKeys = &cobra.Command{
|
var listPreAuthKeys = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List the preauthkeys for this namespace",
|
Short: "List the preauthkeys for this namespace",
|
||||||
|
Aliases: []string{"ls", "show"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -120,6 +122,7 @@ var listPreAuthKeys = &cobra.Command{
|
|||||||
var createPreAuthKeyCmd = &cobra.Command{
|
var createPreAuthKeyCmd = &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "Creates a new preauthkey in the specified namespace",
|
Short: "Creates a new preauthkey in the specified namespace",
|
||||||
|
Aliases: []string{"c", "new"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
@@ -174,6 +177,7 @@ var createPreAuthKeyCmd = &cobra.Command{
|
|||||||
var expirePreAuthKeyCmd = &cobra.Command{
|
var expirePreAuthKeyCmd = &cobra.Command{
|
||||||
Use: "expire KEY",
|
Use: "expire KEY",
|
||||||
Short: "Expire a preauthkey",
|
Short: "Expire a preauthkey",
|
||||||
|
Aliases: []string{"revoke", "exp", "e"},
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return errMissingParameter
|
return errMissingParameter
|
||||||
|
@@ -37,11 +37,13 @@ func init() {
|
|||||||
var routesCmd = &cobra.Command{
|
var routesCmd = &cobra.Command{
|
||||||
Use: "routes",
|
Use: "routes",
|
||||||
Short: "Manage the routes of Headscale",
|
Short: "Manage the routes of Headscale",
|
||||||
|
Aliases: []string{"r", "route"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listRoutesCmd = &cobra.Command{
|
var listRoutesCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List routes advertised and enabled by a given node",
|
Short: "List routes advertised and enabled by a given node",
|
||||||
|
Aliases: []string{"ls", "show"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,12 +18,12 @@ var serveCmd = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatal().Caller().Err(err).Msg("Error initializing")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.Serve()
|
err = h.Serve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatal().Caller().Err(err).Msg("Error starting server")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,9 @@ func LoadConfig(path string) error {
|
|||||||
|
|
||||||
viper.SetDefault("dns_config", nil)
|
viper.SetDefault("dns_config", nil)
|
||||||
|
|
||||||
|
viper.SetDefault("derp.server.enabled", false)
|
||||||
|
viper.SetDefault("derp.server.stun.enabled", true)
|
||||||
|
|
||||||
viper.SetDefault("unix_socket", "/var/run/headscale.sock")
|
viper.SetDefault("unix_socket", "/var/run/headscale.sock")
|
||||||
viper.SetDefault("unix_socket_permission", "0o770")
|
viper.SetDefault("unix_socket_permission", "0o770")
|
||||||
|
|
||||||
@@ -117,6 +120,16 @@ func LoadConfig(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDERPConfig() headscale.DERPConfig {
|
func GetDERPConfig() headscale.DERPConfig {
|
||||||
|
serverEnabled := viper.GetBool("derp.server.enabled")
|
||||||
|
serverRegionID := viper.GetInt("derp.server.region_id")
|
||||||
|
serverRegionCode := viper.GetString("derp.server.region_code")
|
||||||
|
serverRegionName := viper.GetString("derp.server.region_name")
|
||||||
|
stunAddr := viper.GetString("derp.server.stun_listen_addr")
|
||||||
|
|
||||||
|
if serverEnabled && stunAddr == "" {
|
||||||
|
log.Fatal().Msg("derp.server.stun_listen_addr must be set if derp.server.enabled is true")
|
||||||
|
}
|
||||||
|
|
||||||
urlStrs := viper.GetStringSlice("derp.urls")
|
urlStrs := viper.GetStringSlice("derp.urls")
|
||||||
|
|
||||||
urls := make([]url.URL, len(urlStrs))
|
urls := make([]url.URL, len(urlStrs))
|
||||||
@@ -138,6 +151,11 @@ func GetDERPConfig() headscale.DERPConfig {
|
|||||||
updateFrequency := viper.GetDuration("derp.update_frequency")
|
updateFrequency := viper.GetDuration("derp.update_frequency")
|
||||||
|
|
||||||
return headscale.DERPConfig{
|
return headscale.DERPConfig{
|
||||||
|
ServerEnabled: serverEnabled,
|
||||||
|
ServerRegionID: serverRegionID,
|
||||||
|
ServerRegionCode: serverRegionCode,
|
||||||
|
ServerRegionName: serverRegionName,
|
||||||
|
STUNAddr: stunAddr,
|
||||||
URLs: urls,
|
URLs: urls,
|
||||||
Paths: paths,
|
Paths: paths,
|
||||||
AutoUpdate: autoUpdate,
|
AutoUpdate: autoUpdate,
|
||||||
@@ -304,6 +322,7 @@ func getHeadscaleConfig() headscale.Config {
|
|||||||
return headscale.Config{
|
return headscale.Config{
|
||||||
ServerURL: viper.GetString("server_url"),
|
ServerURL: viper.GetString("server_url"),
|
||||||
Addr: viper.GetString("listen_addr"),
|
Addr: viper.GetString("listen_addr"),
|
||||||
|
MetricsAddr: viper.GetString("metrics_listen_addr"),
|
||||||
GRPCAddr: viper.GetString("grpc_listen_addr"),
|
GRPCAddr: viper.GetString("grpc_listen_addr"),
|
||||||
GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"),
|
GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"),
|
||||||
|
|
||||||
|
@@ -55,6 +55,7 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
|||||||
// Test that config file was interpreted correctly
|
// Test that config file was interpreted correctly
|
||||||
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
||||||
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
||||||
|
c.Assert(viper.GetString("metrics_listen_addr"), check.Equals, "127.0.0.1:9090")
|
||||||
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
||||||
c.Assert(viper.GetString("db_path"), check.Equals, "/var/lib/headscale/db.sqlite")
|
c.Assert(viper.GetString("db_path"), check.Equals, "/var/lib/headscale/db.sqlite")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
||||||
|
@@ -16,6 +16,12 @@ server_url: http://127.0.0.1:8080
|
|||||||
#
|
#
|
||||||
listen_addr: 0.0.0.0:8080
|
listen_addr: 0.0.0.0:8080
|
||||||
|
|
||||||
|
# Address to listen to /metrics, you may want
|
||||||
|
# to keep this endpoint private to your internal
|
||||||
|
# network
|
||||||
|
#
|
||||||
|
metrics_listen_addr: 127.0.0.1:9090
|
||||||
|
|
||||||
# Address to listen for gRPC.
|
# Address to listen for gRPC.
|
||||||
# gRPC is used for controlling a headscale server
|
# gRPC is used for controlling a headscale server
|
||||||
# remotely with the CLI
|
# remotely with the CLI
|
||||||
@@ -49,6 +55,26 @@ ip_prefixes:
|
|||||||
# headscale needs a list of DERP servers that can be presented
|
# headscale needs a list of DERP servers that can be presented
|
||||||
# to the clients.
|
# to the clients.
|
||||||
derp:
|
derp:
|
||||||
|
server:
|
||||||
|
# If enabled, runs the embedded DERP server and merges it into the rest of the DERP config
|
||||||
|
# The Headscale server_url defined above MUST be using https, DERP requires TLS to be in place
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Region ID to use for the embedded DERP server.
|
||||||
|
# The local DERP prevails if the region ID collides with other region ID coming from
|
||||||
|
# the regular DERP config.
|
||||||
|
region_id: 999
|
||||||
|
|
||||||
|
# Region code and name are displayed in the Tailscale UI to identify a DERP region
|
||||||
|
region_code: "headscale"
|
||||||
|
region_name: "Headscale Embedded DERP"
|
||||||
|
|
||||||
|
# Listens in UDP at the configured address for STUN connections to help on NAT traversal.
|
||||||
|
# When the embedded DERP server is enabled stun_listen_addr MUST be defined.
|
||||||
|
#
|
||||||
|
# For more details on how this works, check this great article: https://tailscale.com/blog/how-tailscale-works/
|
||||||
|
stun_listen_addr: "0.0.0.0:3478"
|
||||||
|
|
||||||
# List of externally available DERP maps encoded in JSON
|
# List of externally available DERP maps encoded in JSON
|
||||||
urls:
|
urls:
|
||||||
- https://controlplane.tailscale.com/derpmap/default
|
- https://controlplane.tailscale.com/derpmap/default
|
||||||
@@ -117,7 +143,7 @@ tls_client_auth_mode: relaxed
|
|||||||
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
|
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
|
||||||
|
|
||||||
# Type of ACME challenge to use, currently supported types:
|
# Type of ACME challenge to use, currently supported types:
|
||||||
# HTTP-01 or TLS_ALPN-01
|
# HTTP-01 or TLS-ALPN-01
|
||||||
# See [docs/tls.md](docs/tls.md) for more information
|
# See [docs/tls.md](docs/tls.md) for more information
|
||||||
tls_letsencrypt_challenge_type: HTTP-01
|
tls_letsencrypt_challenge_type: HTTP-01
|
||||||
# When HTTP-01 challenge is chosen, letsencrypt must set up a
|
# When HTTP-01 challenge is chosen, letsencrypt must set up a
|
||||||
@@ -132,7 +158,8 @@ tls_key_path: ""
|
|||||||
log_level: info
|
log_level: info
|
||||||
|
|
||||||
# Path to a file containg ACL policies.
|
# Path to a file containg ACL policies.
|
||||||
# Recommended path: /etc/headscale/acl.hujson
|
# ACLs can be defined as YAML or HUJSON.
|
||||||
|
# https://tailscale.com/kb/1018/acls/
|
||||||
acl_policy_path: ""
|
acl_policy_path: ""
|
||||||
|
|
||||||
## DNS
|
## DNS
|
||||||
|
109
db.go
109
db.go
@@ -1,13 +1,19 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"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"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,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
|
||||||
@@ -141,3 +179,74 @@ func (h *Headscale) setValue(key string, value string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a "wrapper" type around tailscales
|
||||||
|
// Hostinfo to allow us to add database "serialization"
|
||||||
|
// methods. This allows us to use a typed values throughout
|
||||||
|
// the code and not have to marshal/unmarshal and error
|
||||||
|
// check all over the code.
|
||||||
|
type HostInfo tailcfg.Hostinfo
|
||||||
|
|
||||||
|
func (hi *HostInfo) Scan(destination interface{}) error {
|
||||||
|
switch value := destination.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(value, hi)
|
||||||
|
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(value), hi)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value return json value, implement driver.Valuer interface.
|
||||||
|
func (hi HostInfo) Value() (driver.Value, error) {
|
||||||
|
bytes, err := json.Marshal(hi)
|
||||||
|
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPPrefixes []netaddr.IPPrefix
|
||||||
|
|
||||||
|
func (i *IPPrefixes) Scan(destination interface{}) error {
|
||||||
|
switch value := destination.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(value, i)
|
||||||
|
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(value), i)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value return json value, implement driver.Valuer interface.
|
||||||
|
func (i IPPrefixes) Value() (driver.Value, error) {
|
||||||
|
bytes, err := json.Marshal(i)
|
||||||
|
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringList []string
|
||||||
|
|
||||||
|
func (i *StringList) Scan(destination interface{}) error {
|
||||||
|
switch value := destination.(type) {
|
||||||
|
case []byte:
|
||||||
|
return json.Unmarshal(value, i)
|
||||||
|
|
||||||
|
case string:
|
||||||
|
return json.Unmarshal([]byte(value), i)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value return json value, implement driver.Valuer interface.
|
||||||
|
func (i StringList) Value() (driver.Value, error) {
|
||||||
|
bytes, err := json.Marshal(i)
|
||||||
|
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
1
derp.go
1
derp.go
@@ -148,6 +148,7 @@ func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
log.Info().Msg("Fetching DERPMap updates")
|
log.Info().Msg("Fetching DERPMap updates")
|
||||||
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
||||||
|
h.DERPMap.Regions[h.DERPServer.region.RegionID] = &h.DERPServer.region
|
||||||
|
|
||||||
namespaces, err := h.ListNamespaces()
|
namespaces, err := h.ListNamespaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
231
derp_server.go
Normal file
231
derp_server.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"tailscale.com/derp"
|
||||||
|
"tailscale.com/net/stun"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fastStartHeader is the header (with value "1") that signals to the HTTP
|
||||||
|
// server that the DERP HTTP client does not want the HTTP 101 response
|
||||||
|
// headers and it will begin writing & reading the DERP protocol immediately
|
||||||
|
// following its HTTP request.
|
||||||
|
const fastStartHeader = "Derp-Fast-Start"
|
||||||
|
|
||||||
|
type DERPServer struct {
|
||||||
|
tailscaleDERP *derp.Server
|
||||||
|
region tailcfg.DERPRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) NewDERPServer() (*DERPServer, error) {
|
||||||
|
server := derp.NewServer(key.NodePrivate(*h.privateKey), log.Info().Msgf)
|
||||||
|
region, err := h.generateRegionLocalDERP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DERPServer{server, region}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) generateRegionLocalDERP() (tailcfg.DERPRegion, error) {
|
||||||
|
serverURL, err := url.Parse(h.cfg.ServerURL)
|
||||||
|
if err != nil {
|
||||||
|
return tailcfg.DERPRegion{}, err
|
||||||
|
}
|
||||||
|
var host string
|
||||||
|
var port int
|
||||||
|
host, portStr, err := net.SplitHostPort(serverURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
if serverURL.Scheme == "https" {
|
||||||
|
host = serverURL.Host
|
||||||
|
port = 443
|
||||||
|
} else {
|
||||||
|
host = serverURL.Host
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port, err = strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return tailcfg.DERPRegion{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localDERPregion := tailcfg.DERPRegion{
|
||||||
|
RegionID: h.cfg.DERP.ServerRegionID,
|
||||||
|
RegionCode: h.cfg.DERP.ServerRegionCode,
|
||||||
|
RegionName: h.cfg.DERP.ServerRegionName,
|
||||||
|
Avoid: false,
|
||||||
|
Nodes: []*tailcfg.DERPNode{
|
||||||
|
{
|
||||||
|
Name: fmt.Sprintf("%d", h.cfg.DERP.ServerRegionID),
|
||||||
|
RegionID: h.cfg.DERP.ServerRegionID,
|
||||||
|
HostName: host,
|
||||||
|
DERPPort: port,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, portSTUNStr, err := net.SplitHostPort(h.cfg.DERP.STUNAddr)
|
||||||
|
if err != nil {
|
||||||
|
return tailcfg.DERPRegion{}, err
|
||||||
|
}
|
||||||
|
portSTUN, err := strconv.Atoi(portSTUNStr)
|
||||||
|
if err != nil {
|
||||||
|
return tailcfg.DERPRegion{}, err
|
||||||
|
}
|
||||||
|
localDERPregion.Nodes[0].STUNPort = portSTUN
|
||||||
|
|
||||||
|
return localDERPregion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) DERPHandler(ctx *gin.Context) {
|
||||||
|
log.Trace().Caller().Msgf("/derp request from %v", ctx.ClientIP())
|
||||||
|
up := strings.ToLower(ctx.Request.Header.Get("Upgrade"))
|
||||||
|
if up != "websocket" && up != "derp" {
|
||||||
|
if up != "" {
|
||||||
|
log.Warn().Caller().Msgf("Weird websockets connection upgrade: %q", up)
|
||||||
|
}
|
||||||
|
ctx.String(http.StatusUpgradeRequired, "DERP requires connection upgrade")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fastStart := ctx.Request.Header.Get(fastStartHeader) == "1"
|
||||||
|
|
||||||
|
hijacker, ok := ctx.Writer.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Caller().Msg("DERP requires Hijacker interface from Gin")
|
||||||
|
ctx.String(http.StatusInternalServerError, "HTTP does not support general TCP support")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn, conn, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Caller().Err(err).Msgf("Hijack failed")
|
||||||
|
ctx.String(http.StatusInternalServerError, "HTTP does not support general TCP support")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fastStart {
|
||||||
|
pubKey := h.privateKey.Public()
|
||||||
|
pubKeyStr := pubKey.UntypedHexString() // nolint
|
||||||
|
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
|
||||||
|
"Upgrade: DERP\r\n"+
|
||||||
|
"Connection: Upgrade\r\n"+
|
||||||
|
"Derp-Version: %v\r\n"+
|
||||||
|
"Derp-Public-Key: %s\r\n\r\n",
|
||||||
|
derp.ProtocolVersion,
|
||||||
|
pubKeyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.DERPServer.tailscaleDERP.Accept(netConn, conn, netConn.RemoteAddr().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DERPProbeHandler is the endpoint that js/wasm clients hit to measure
|
||||||
|
// DERP latency, since they can't do UDP STUN queries.
|
||||||
|
func (h *Headscale) DERPProbeHandler(ctx *gin.Context) {
|
||||||
|
switch ctx.Request.Method {
|
||||||
|
case "HEAD", "GET":
|
||||||
|
ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
default:
|
||||||
|
ctx.String(http.StatusMethodNotAllowed, "bogus probe method")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DERPBootstrapDNSHandler implements the /bootsrap-dns endpoint
|
||||||
|
// Described in https://github.com/tailscale/tailscale/issues/1405,
|
||||||
|
// this endpoint provides a way to help a client when it fails to start up
|
||||||
|
// because its DNS are broken.
|
||||||
|
// The initial implementation is here https://github.com/tailscale/tailscale/pull/1406
|
||||||
|
// They have a cache, but not clear if that is really necessary at Headscale, uh, scale.
|
||||||
|
// An example implementation is found here https://derp.tailscale.com/bootstrap-dns
|
||||||
|
func (h *Headscale) DERPBootstrapDNSHandler(ctx *gin.Context) {
|
||||||
|
dnsEntries := make(map[string][]net.IP)
|
||||||
|
|
||||||
|
resolvCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
var r net.Resolver
|
||||||
|
for _, region := range h.DERPMap.Regions {
|
||||||
|
for _, node := range region.Nodes { // we don't care if we override some nodes
|
||||||
|
addrs, err := r.LookupIP(resolvCtx, "ip", node.HostName)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace().Caller().Err(err).Msgf("bootstrap DNS lookup failed %q", node.HostName)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dnsEntries[node.HostName] = addrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, dnsEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeSTUN starts a STUN server on the configured addr.
|
||||||
|
func (h *Headscale) ServeSTUN() {
|
||||||
|
packetConn, err := net.ListenPacket("udp", h.cfg.DERP.STUNAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Msgf("failed to open STUN listener: %v", err)
|
||||||
|
}
|
||||||
|
log.Info().Msgf("STUN server started at %s", packetConn.LocalAddr())
|
||||||
|
|
||||||
|
udpConn, ok := packetConn.(*net.UDPConn)
|
||||||
|
if !ok {
|
||||||
|
log.Fatal().Msg("STUN listener is not a UDP listener")
|
||||||
|
}
|
||||||
|
serverSTUNListener(context.Background(), udpConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverSTUNListener(ctx context.Context, packetConn *net.UDPConn) {
|
||||||
|
var buf [64 << 10]byte
|
||||||
|
var (
|
||||||
|
bytesRead int
|
||||||
|
udpAddr *net.UDPAddr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
bytesRead, udpAddr, err = packetConn.ReadFromUDP(buf[:])
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Error().Caller().Err(err).Msgf("STUN ReadFrom")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Trace().Caller().Msgf("STUN request from %v", udpAddr)
|
||||||
|
pkt := buf[:bytesRead]
|
||||||
|
if !stun.Is(pkt) {
|
||||||
|
log.Trace().Caller().Msgf("UDP packet is not STUN")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txid, err := stun.ParseBindingRequest(pkt)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace().Caller().Err(err).Msgf("STUN parse error")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res := stun.Response(txid, udpAddr.IP, uint16(udpAddr.Port))
|
||||||
|
_, err = packetConn.WriteTo(res, udpAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace().Caller().Err(err).Msgf("Issue writing to UDP")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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),
|
||||||
|
@@ -5,4 +5,5 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
server_url: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
server_url: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
listen_addr: "0.0.0.0:8080"
|
listen_addr: "0.0.0.0:8080"
|
||||||
|
metrics_listen_addr: "127.0.0.1:9090"
|
||||||
ephemeral_node_inactivity_timeout: "30m"
|
ephemeral_node_inactivity_timeout: "30m"
|
||||||
|
@@ -25,6 +25,11 @@ spec:
|
|||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: headscale-config
|
name: headscale-config
|
||||||
key: listen_addr
|
key: listen_addr
|
||||||
|
- name: METRICS_LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: metrics_listen_addr
|
||||||
- name: DERP_MAP_PATH
|
- name: DERP_MAP_PATH
|
||||||
value: /vol/config/derp.yaml
|
value: /vol/config/derp.yaml
|
||||||
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
@@ -26,6 +26,11 @@ spec:
|
|||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: headscale-config
|
name: headscale-config
|
||||||
key: listen_addr
|
key: listen_addr
|
||||||
|
- name: METRICS_LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: metrics_listen_addr
|
||||||
- name: DERP_MAP_PATH
|
- name: DERP_MAP_PATH
|
||||||
value: /vol/config/derp.yaml
|
value: /vol/config/derp.yaml
|
||||||
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
362
docs/proposals/001-acls.md
Normal file
362
docs/proposals/001-acls.md
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# ACLs
|
||||||
|
|
||||||
|
A key component of tailscale is the notion of Tailnet. This notion is hidden
|
||||||
|
but the implications that it have on how to use tailscale are not.
|
||||||
|
|
||||||
|
For tailscale an [tailnet](https://tailscale.com/kb/1136/tailnet/) is the
|
||||||
|
following:
|
||||||
|
|
||||||
|
> For personal users, you are a tailnet of many devices and one person. Each
|
||||||
|
> device gets a private Tailscale IP address in the CGNAT range and every
|
||||||
|
> device can talk directly to every other device, wherever they are on the
|
||||||
|
> internet.
|
||||||
|
>
|
||||||
|
> For businesses and organizations, a tailnet is many devices and many users.
|
||||||
|
> It can be based on your Microsoft Active Directory, your Google Workspace, a
|
||||||
|
> GitHub organization, Okta tenancy, or other identity provider namespace. All
|
||||||
|
> of the devices and users in your tailnet can be seen by the tailnet
|
||||||
|
> administrators in the Tailscale admin console. There you can apply
|
||||||
|
> tailnet-wide configuration, such as ACLs that affect visibility of devices
|
||||||
|
> inside your tailnet, DNS settings, and more.
|
||||||
|
|
||||||
|
## Current implementation and issues
|
||||||
|
|
||||||
|
Currently in headscale, the namespaces are used both as tailnet and users. The
|
||||||
|
issue is that if we want to use the ACL's we can't use both at the same time.
|
||||||
|
|
||||||
|
Tailnet's cannot communicate with each others. So we can't have an ACL that
|
||||||
|
authorize tailnet (namespace) A to talk to tailnet (namespace) B.
|
||||||
|
|
||||||
|
We also can't write ACLs based on the users (namespaces in headscale) since all
|
||||||
|
devices belong to the same user.
|
||||||
|
|
||||||
|
With the current implementation the only ACL that we can user is to associate
|
||||||
|
each headscale IP to a host manually then write the ACLs according to this
|
||||||
|
manual mapping.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hosts": {
|
||||||
|
"host1": "100.64.0.1",
|
||||||
|
"server": "100.64.0.2"
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{ "action": "accept", "users": ["host1"], "ports": ["host2:80,443"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
While this works, it requires a lot of manual editing on the configuration and
|
||||||
|
to keep track of all devices IP address.
|
||||||
|
|
||||||
|
## Proposition for a next implementation
|
||||||
|
|
||||||
|
In order to ease the use of ACL's we need to split the tailnet and users
|
||||||
|
notion.
|
||||||
|
|
||||||
|
A solution could be to consider a headscale server (in it's entirety) as a
|
||||||
|
tailnet.
|
||||||
|
|
||||||
|
For personal users the default behavior could either allow all communications
|
||||||
|
between all namespaces (like tailscale) or dissallow all communications between
|
||||||
|
namespaces (current behavior).
|
||||||
|
|
||||||
|
For businesses and organisations, viewing a headscale instance a single tailnet
|
||||||
|
would allow users (namespace) to talk to each other with the ACLs. As described
|
||||||
|
in tailscale's documentation [[1]], a server should be tagged and personnal
|
||||||
|
devices should be tied to a user. Translated in headscale's terms each user can
|
||||||
|
have multiple devices and all those devices should be in the same namespace.
|
||||||
|
The servers should be tagged and used as such.
|
||||||
|
|
||||||
|
This implementation would render useless the sharing feature that is currently
|
||||||
|
implemented since an ACL could do the same. Simplifying to only one user
|
||||||
|
interface to do one thing is easier and less confusing for the users.
|
||||||
|
|
||||||
|
To better suit the ACLs in this proposition, it's advised to consider that each
|
||||||
|
namespaces belong to one person. This person can have multiple devices, they
|
||||||
|
will all be considered as the same user in the ACLs. OIDC feature wouldn't need
|
||||||
|
to map people to namespace, just create a namespace if the person isn't
|
||||||
|
registered yet.
|
||||||
|
|
||||||
|
As a sidenote, users would like to write ACLs as YAML. We should offer users
|
||||||
|
the ability to rules in either format (HuJSON or YAML).
|
||||||
|
|
||||||
|
[1]: https://tailscale.com/kb/1068/acl-tags/
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Let's build an example use case for a small business (It may be the place where
|
||||||
|
ACL's are the most useful).
|
||||||
|
|
||||||
|
We have a small company with a boss, an admin, two developper and an intern.
|
||||||
|
|
||||||
|
The boss should have access to all servers but not to the users hosts. Admin
|
||||||
|
should also have access to all hosts except that their permissions should be
|
||||||
|
limited to maintaining the hosts (for example purposes). The developers can do
|
||||||
|
anything they want on dev hosts, but only watch on productions hosts. Intern
|
||||||
|
can only interact with the development servers.
|
||||||
|
|
||||||
|
Each user have at least a device connected to the network and we have some
|
||||||
|
servers.
|
||||||
|
|
||||||
|
- database.prod
|
||||||
|
- database.dev
|
||||||
|
- app-server1.prod
|
||||||
|
- app-server1.dev
|
||||||
|
- billing.internal
|
||||||
|
|
||||||
|
### Current headscale implementation
|
||||||
|
|
||||||
|
Let's create some namespaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
headscale namespaces create prod
|
||||||
|
headscale namespaces create dev
|
||||||
|
headscale namespaces create internal
|
||||||
|
headscale namespaces create users
|
||||||
|
|
||||||
|
headscale nodes register -n users boss-computer
|
||||||
|
headscale nodes register -n users admin1-computer
|
||||||
|
headscale nodes register -n users dev1-computer
|
||||||
|
headscale nodes register -n users dev1-phone
|
||||||
|
headscale nodes register -n users dev2-computer
|
||||||
|
headscale nodes register -n users intern1-computer
|
||||||
|
|
||||||
|
headscale nodes register -n prod database
|
||||||
|
headscale nodes register -n prod app-server1
|
||||||
|
|
||||||
|
headscale nodes register -n dev database
|
||||||
|
headscale nodes register -n dev app-server1
|
||||||
|
|
||||||
|
headscale nodes register -n internal billing
|
||||||
|
|
||||||
|
headscale nodes list
|
||||||
|
ID | Name | Namespace | IP address
|
||||||
|
1 | boss-computer | users | 100.64.0.1
|
||||||
|
2 | admin1-computer | users | 100.64.0.2
|
||||||
|
3 | dev1-computer | users | 100.64.0.3
|
||||||
|
4 | dev1-phone | users | 100.64.0.4
|
||||||
|
5 | dev2-computer | users | 100.64.0.5
|
||||||
|
6 | intern1-computer | users | 100.64.0.6
|
||||||
|
7 | database | prod | 100.64.0.7
|
||||||
|
8 | app-server1 | prod | 100.64.0.8
|
||||||
|
9 | database | dev | 100.64.0.9
|
||||||
|
10 | app-server1 | dev | 100.64.0.10
|
||||||
|
11 | internal | internal | 100.64.0.11
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to only allow the communications related to our description above we
|
||||||
|
need to add the following ACLs
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hosts": {
|
||||||
|
"boss-computer": "100.64.0.1",
|
||||||
|
"admin1-computer": "100.64.0.2",
|
||||||
|
"dev1-computer": "100.64.0.3",
|
||||||
|
"dev1-phone": "100.64.0.4",
|
||||||
|
"dev2-computer": "100.64.0.5",
|
||||||
|
"intern1-computer": "100.64.0.6",
|
||||||
|
"prod-app-server1": "100.64.0.8"
|
||||||
|
},
|
||||||
|
"groups": {
|
||||||
|
"group:dev": ["dev1-computer", "dev1-phone", "dev2-computer"],
|
||||||
|
"group:admin": ["admin1-computer"],
|
||||||
|
"group:boss": ["boss-computer"],
|
||||||
|
"group:intern": ["intern1-computer"]
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
// boss have access to all servers but no users hosts
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:boss"],
|
||||||
|
"ports": ["prod:*", "dev:*", "internal:*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// admin have access to adminstration port (lets only consider port 22 here)
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:admin"],
|
||||||
|
"ports": ["prod:22", "dev:22", "internal:22"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// dev can do anything on dev servers and check access on prod servers
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:dev"],
|
||||||
|
"ports": ["dev:*", "prod-app-server1:80,443"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// interns only have access to port 80 and 443 on dev servers (lame internship)
|
||||||
|
{ "action": "accept", "users": ["group:intern"], "ports": ["dev:80,443"] },
|
||||||
|
|
||||||
|
// users can access their own devices
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["dev1-computer"],
|
||||||
|
"ports": ["dev1-phone:*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["dev1-phone"],
|
||||||
|
"ports": ["dev1-computer:*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// internal namespace communications should still be allowed within the namespace
|
||||||
|
{ "action": "accept", "users": ["dev"], "ports": ["dev:*"] },
|
||||||
|
{ "action": "accept", "users": ["prod"], "ports": ["prod:*"] },
|
||||||
|
{ "action": "accept", "users": ["internal"], "ports": ["internal:*"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since communications between namespace isn't possible we also have to share the
|
||||||
|
devices between the namespaces.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
// add boss host to prod, dev and internal network
|
||||||
|
headscale nodes share -i 1 -n prod
|
||||||
|
headscale nodes share -i 1 -n dev
|
||||||
|
headscale nodes share -i 1 -n internal
|
||||||
|
|
||||||
|
// add admin computer to prod, dev and internal network
|
||||||
|
headscale nodes share -i 2 -n prod
|
||||||
|
headscale nodes share -i 2 -n dev
|
||||||
|
headscale nodes share -i 2 -n internal
|
||||||
|
|
||||||
|
// add all dev to prod and dev network
|
||||||
|
headscale nodes share -i 3 -n dev
|
||||||
|
headscale nodes share -i 4 -n dev
|
||||||
|
headscale nodes share -i 3 -n prod
|
||||||
|
headscale nodes share -i 4 -n prod
|
||||||
|
headscale nodes share -i 5 -n dev
|
||||||
|
headscale nodes share -i 5 -n prod
|
||||||
|
|
||||||
|
headscale nodes share -i 6 -n dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This fake network have not been tested but it should work. Operating it could
|
||||||
|
be quite tedious if the company grows. Each time a new user join we have to add
|
||||||
|
it to a group, and share it to the correct namespaces. If the user want
|
||||||
|
multiple devices we have to allow communication to each of them one by one. If
|
||||||
|
business conduct a change in the organisations we may have to rewrite all acls
|
||||||
|
and reorganise all namespaces.
|
||||||
|
|
||||||
|
If we add servers in production we should also update the ACLs to allow dev
|
||||||
|
access to certain category of them (only app servers for example).
|
||||||
|
|
||||||
|
### example based on the proposition in this document
|
||||||
|
|
||||||
|
Let's create the namespaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
headscale namespaces create boss
|
||||||
|
headscale namespaces create admin1
|
||||||
|
headscale namespaces create dev1
|
||||||
|
headscale namespaces create dev2
|
||||||
|
headscale namespaces create intern1
|
||||||
|
```
|
||||||
|
|
||||||
|
We don't need to create namespaces for the servers because the servers will be
|
||||||
|
tagged. When registering the servers we will need to add the flag
|
||||||
|
`--advertised-tags=tag:<tag1>,tag:<tag2>`, and the user (namespace) that is
|
||||||
|
registering the server should be allowed to do it. Since anyone can add tags to
|
||||||
|
a server they can register, the check of the tags is done on headscale server
|
||||||
|
and only valid tags are applied. A tag is valid if the namespace that is
|
||||||
|
registering it is allowed to do it.
|
||||||
|
|
||||||
|
Here are the ACL's to implement the same permissions as above:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// groups are simpler and only list the namespaces name
|
||||||
|
"groups": {
|
||||||
|
"group:boss": ["boss"],
|
||||||
|
"group:dev": ["dev1", "dev2"],
|
||||||
|
"group:admin": ["admin1"],
|
||||||
|
"group:intern": ["intern1"]
|
||||||
|
},
|
||||||
|
"tagOwners": {
|
||||||
|
// the administrators can add servers in production
|
||||||
|
"tag:prod-databases": ["group:admin"],
|
||||||
|
"tag:prod-app-servers": ["group:admin"],
|
||||||
|
|
||||||
|
// the boss can tag any server as internal
|
||||||
|
"tag:internal": ["group:boss"],
|
||||||
|
|
||||||
|
// dev can add servers for dev purposes as well as admins
|
||||||
|
"tag:dev-databases": ["group:admin", "group:dev"],
|
||||||
|
"tag:dev-app-servers": ["group:admin", "group:dev"]
|
||||||
|
|
||||||
|
// interns cannot add servers
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
// boss have access to all servers
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:boss"],
|
||||||
|
"ports": [
|
||||||
|
"tag:prod-databases:*",
|
||||||
|
"tag:prod-app-servers:*",
|
||||||
|
"tag:internal:*",
|
||||||
|
"tag:dev-databases:*",
|
||||||
|
"tag:dev-app-servers:*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// admin have only access to administrative ports of the servers
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:admin"],
|
||||||
|
"ports": [
|
||||||
|
"tag:prod-databases:22",
|
||||||
|
"tag:prod-app-servers:22",
|
||||||
|
"tag:internal:22",
|
||||||
|
"tag:dev-databases:22",
|
||||||
|
"tag:dev-app-servers:22"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:dev"],
|
||||||
|
"ports": [
|
||||||
|
"tag:dev-databases:*",
|
||||||
|
"tag:dev-app-servers:*",
|
||||||
|
"tag:prod-app-servers:80,443"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// servers should be able to talk to database. Database should not be able to initiate connections to server
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["tag:dev-app-servers"],
|
||||||
|
"ports": ["tag:dev-databases:5432"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["tag:prod-app-servers"],
|
||||||
|
"ports": ["tag:prod-databases:5432"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// interns have access to dev-app-servers only in reading mode
|
||||||
|
{
|
||||||
|
"action": "accept",
|
||||||
|
"users": ["group:intern"],
|
||||||
|
"ports": ["tag:dev-app-servers:80,443"]
|
||||||
|
},
|
||||||
|
|
||||||
|
// we still have to allow internal namespaces communications since nothing guarantees that each user have their own namespaces. This could be talked over.
|
||||||
|
{ "action": "accept", "users": ["boss"], "ports": ["boss:*"] },
|
||||||
|
{ "action": "accept", "users": ["dev1"], "ports": ["dev1:*"] },
|
||||||
|
{ "action": "accept", "users": ["dev2"], "ports": ["dev2:*"] },
|
||||||
|
{ "action": "accept", "users": ["admin1"], "ports": ["admin1:*"] },
|
||||||
|
{ "action": "accept", "users": ["intern1"], "ports": ["intern1:*"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With this implementation, the sharing step is not necessary. Maintenance cost
|
||||||
|
of the ACL file is lower and less tedious (no need to map hostname and IP's
|
||||||
|
into it).
|
@@ -55,6 +55,7 @@ docker run \
|
|||||||
--rm \
|
--rm \
|
||||||
--volume $(pwd)/config:/etc/headscale/ \
|
--volume $(pwd)/config:/etc/headscale/ \
|
||||||
--publish 127.0.0.1:8080:8080 \
|
--publish 127.0.0.1:8080:8080 \
|
||||||
|
--publish 127.0.0.1:9090:9090 \
|
||||||
headscale/headscale:<VERSION> \
|
headscale/headscale:<VERSION> \
|
||||||
headscale serve
|
headscale serve
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ docker ps
|
|||||||
Verify `headscale` is available:
|
Verify `headscale` is available:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl http://127.0.0.1:8080/metrics
|
curl http://127.0.0.1:9090/metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
6. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
||||||
|
@@ -67,7 +67,7 @@ To run `headscale` in the background, please follow the steps in the [SystemD se
|
|||||||
Verify `headscale` is available:
|
Verify `headscale` is available:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl http://127.0.0.1:8080/metrics
|
curl http://127.0.0.1:9090/metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
8. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
8. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
39
go.mod
39
go.mod
@@ -1,9 +1,10 @@
|
|||||||
module github.com/juanfont/headscale
|
module github.com/juanfont/headscale
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||||
|
github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
github.com/efekarakus/termcolor v1.0.1
|
github.com/efekarakus/termcolor v1.0.1
|
||||||
github.com/fatih/set v0.2.1
|
github.com/fatih/set v0.2.1
|
||||||
@@ -13,12 +14,12 @@ require (
|
|||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3
|
||||||
github.com/infobloxopen/protoc-gen-gorm v1.1.0
|
github.com/infobloxopen/protoc-gen-gorm v1.1.0
|
||||||
github.com/klauspost/compress v1.14.2
|
github.com/klauspost/compress v1.14.4
|
||||||
github.com/ory/dockertest/v3 v3.8.1
|
github.com/ory/dockertest/v3 v3.8.1
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||||
github.com/prometheus/client_golang v1.12.1
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/pterm/pterm v0.12.36
|
github.com/pterm/pterm v0.12.37
|
||||||
github.com/rs/zerolog v1.26.1
|
github.com/rs/zerolog v1.26.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.3.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.10.1
|
||||||
@@ -27,38 +28,40 @@ require (
|
|||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||||
github.com/zsais/go-gin-prometheus v0.1.0
|
github.com/zsais/go-gin-prometheus v0.1.0
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336
|
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7
|
||||||
google.golang.org/grpc v1.44.0
|
google.golang.org/grpc v1.44.0
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.27.1
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gorm.io/datatypes v1.0.5
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
gorm.io/driver/postgres v1.3.1
|
gorm.io/driver/postgres v1.3.1
|
||||||
gorm.io/gorm v1.23.1
|
gorm.io/gorm v1.23.1
|
||||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
||||||
tailscale.com v1.20.4
|
tailscale.com v1.22.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
github.com/atomicgo/cursor v0.0.1 // indirect
|
github.com/atomicgo/cursor v0.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/containerd/continuity v0.2.2 // indirect
|
github.com/containerd/continuity v0.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/denisenkom/go-mssqldb v0.12.0 // indirect
|
||||||
github.com/docker/cli v20.10.12+incompatible // indirect
|
github.com/docker/cli v20.10.12+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.12+incompatible // indirect
|
github.com/docker/docker v20.10.12+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.14.7 // indirect
|
github.com/glebarez/go-sqlite v1.14.8 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||||
@@ -92,13 +95,14 @@ require (
|
|||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/lib/pq v1.10.3 // indirect
|
github.com/lib/pq v1.10.3 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.11 // indirect
|
github.com/mattn/go-sqlite3 v1.14.11 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
@@ -121,7 +125,7 @@ require (
|
|||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
@@ -129,20 +133,17 @@ require (
|
|||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||||
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
|
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
modernc.org/libc v1.14.5 // indirect
|
||||||
gorm.io/driver/mysql v1.3.2 // indirect
|
|
||||||
gorm.io/driver/sqlite v1.3.1 // indirect
|
|
||||||
gorm.io/driver/sqlserver v1.3.1 // indirect
|
|
||||||
modernc.org/libc v1.14.3 // indirect
|
|
||||||
modernc.org/mathutil v1.4.1 // indirect
|
modernc.org/mathutil v1.4.1 // indirect
|
||||||
modernc.org/memory v1.0.5 // indirect
|
modernc.org/memory v1.0.5 // indirect
|
||||||
modernc.org/sqlite v1.14.5 // indirect
|
modernc.org/sqlite v1.14.7 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
85
go.sum
85
go.sum
@@ -73,14 +73,17 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX
|
|||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
|
||||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
|
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||||
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||||
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
@@ -105,6 +108,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM=
|
github.com/bufbuild/buf v0.37.0/go.mod h1:lQ1m2HkIaGOFba6w/aC3KYBHhKEOESP3gaAEpS3dAFM=
|
||||||
|
github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029 h1:POmUHfxXdeyM8Aomg4tKDcwATCFuW+cYLkj6pwsw9pc=
|
||||||
|
github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029/go.mod h1:Rpr5n9cGHYdM3S3IK8ROSUUUYjQOu+MSUCZDcJbYWi8=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@@ -204,6 +209,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
|||||||
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
|
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
@@ -212,8 +218,9 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
github.com/glebarez/go-sqlite v1.14.7 h1:eXrKp59O5eWBfxv2Xfq5d7uex4+clKrOtWfMzzGSkoM=
|
|
||||||
github.com/glebarez/go-sqlite v1.14.7/go.mod h1:TKAw5tjyB/ocvVht7Xv4772qRAun5CG/xLCEbkDwNUc=
|
github.com/glebarez/go-sqlite v1.14.7/go.mod h1:TKAw5tjyB/ocvVht7Xv4772qRAun5CG/xLCEbkDwNUc=
|
||||||
|
github.com/glebarez/go-sqlite v1.14.8 h1:30RsIS/olgfOMr7SxiCaYhpq50BTteA/CUKaWVOOHYg=
|
||||||
|
github.com/glebarez/go-sqlite v1.14.8/go.mod h1:gf9QVsKCYMcu+7nd+ZbDqvXnEXEb22qLcqRUQ9XEI34=
|
||||||
github.com/glebarez/sqlite v1.3.5 h1:R9op5nxb9Z10t4VXQSdAVyqRalLhWdLrlaT/iuvOGHI=
|
github.com/glebarez/sqlite v1.3.5 h1:R9op5nxb9Z10t4VXQSdAVyqRalLhWdLrlaT/iuvOGHI=
|
||||||
github.com/glebarez/sqlite v1.3.5/go.mod h1:ZffEtp/afVhV+jvIzQi8wlYEIkuGAYshr9OPKM/NmQc=
|
github.com/glebarez/sqlite v1.3.5/go.mod h1:ZffEtp/afVhV+jvIzQi8wlYEIkuGAYshr9OPKM/NmQc=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
@@ -412,7 +419,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
|||||||
github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M=
|
github.com/infobloxopen/atlas-app-toolkit v0.24.1-0.20210416193901-4c7518b07e08/go.mod h1:9BTHnpff654rY1J8KxSUOLJ+ZUDn2Vi3mmk26gQDo1M=
|
||||||
github.com/infobloxopen/protoc-gen-gorm v1.1.0 h1:l6JKEkqMTFbtoGIfQmh/aOy7KfljgX4ql772LtG4kas=
|
github.com/infobloxopen/protoc-gen-gorm v1.1.0 h1:l6JKEkqMTFbtoGIfQmh/aOy7KfljgX4ql772LtG4kas=
|
||||||
github.com/infobloxopen/protoc-gen-gorm v1.1.0/go.mod h1:ohzLmmFMWQztw2RBHunfjKSCjTPUW4JvbgU1Mdazwxg=
|
github.com/infobloxopen/protoc-gen-gorm v1.1.0/go.mod h1:ohzLmmFMWQztw2RBHunfjKSCjTPUW4JvbgU1Mdazwxg=
|
||||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
|
||||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
@@ -434,7 +440,6 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W
|
|||||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
|
||||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
@@ -450,7 +455,6 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
|
|||||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
|
||||||
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
|
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
|
||||||
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
@@ -458,7 +462,6 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
|
|||||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
|
|
||||||
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M=
|
||||||
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
|
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
|
||||||
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
|
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
|
||||||
@@ -474,8 +477,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
@@ -497,8 +498,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
|
|||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw=
|
github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
|
||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
@@ -535,8 +536,9 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
|
|||||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
@@ -558,7 +560,6 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
|
|||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
|
github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
@@ -574,6 +575,8 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT
|
|||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
@@ -671,8 +674,8 @@ github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY
|
|||||||
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
|
||||||
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
|
||||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||||
github.com/pterm/pterm v0.12.36 h1:Ui5zZj7xA8lXR0CxWXlKGCQMW1cZVUMOS8jEXs6ur/g=
|
github.com/pterm/pterm v0.12.37 h1:QGOyuaDUmY3yTbP0k6i0uPNqNHA9YofEBQDy0tIyKTA=
|
||||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
github.com/pterm/pterm v0.12.37/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
@@ -762,10 +765,10 @@ github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9
|
|||||||
github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
github.com/twitchtv/twirp v7.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
@@ -845,8 +848,6 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig=
|
|
||||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -940,8 +941,9 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||||
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -959,8 +961,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
|
|||||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -1063,9 +1066,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -1086,6 +1090,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||||
|
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -1274,8 +1280,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6
|
|||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 h1:RK2ysGpQApbI6U7xn+ROT2rrm08lE/t8AcGqG8XI1CY=
|
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
|
||||||
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
@@ -1359,23 +1365,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/datatypes v1.0.5 h1:3vHCfg4Bz8SDx83zE+ASskF+g/j0kWrcKrY9jFUyAl0=
|
|
||||||
gorm.io/datatypes v1.0.5/go.mod h1:acG/OHGwod+1KrbwPL1t+aavb7jOBOETeyl5M8K5VQs=
|
|
||||||
gorm.io/driver/mysql v1.2.2/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
|
||||||
gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
|
|
||||||
gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
|
||||||
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
|
|
||||||
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
|
|
||||||
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
|
|
||||||
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
|
|
||||||
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
|
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
|
||||||
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
|
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
|
||||||
gorm.io/driver/sqlite v1.3.1 h1:bwfE+zTEWklBYoEodIOIBwuWHpnx52Z9zJFW5F33WLk=
|
|
||||||
gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg=
|
|
||||||
gorm.io/driver/sqlserver v1.3.1 h1:F5t6ScMzOgy1zukRTIZgLZwKahgt3q1woAILVolKpOI=
|
|
||||||
gorm.io/driver/sqlserver v1.3.1/go.mod h1:w25Vrx2BG+CJNUu/xKbFhaKlGxT/nzRkhWCCoptX8tQ=
|
|
||||||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
|
||||||
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
|
||||||
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
|
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
|
||||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
@@ -1450,7 +1441,10 @@ modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6
|
|||||||
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
modernc.org/ccgo/v3 v3.15.1/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||||
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||||
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
||||||
|
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
|
||||||
|
modernc.org/ccgo/v3 v3.15.13/go.mod h1:QHtvdpeODlXjdK3tsbpyK+7U9JV4PQsrPGIbtmc0KfY=
|
||||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
|
modernc.org/ccorpus v1.11.4/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||||
@@ -1494,8 +1488,9 @@ modernc.org/libc v1.13.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
|||||||
modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
modernc.org/libc v1.13.2/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||||
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||||
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
||||||
modernc.org/libc v1.14.3 h1:ruQJ8VDhnWkUR/otUG/Ksw+sWHUw9cPAq6mjDaY/Y7c=
|
|
||||||
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
||||||
|
modernc.org/libc v1.14.5 h1:DAHvwGoVRDZs5iJXnX9RJrgXSsorupCWmJ2ac964Owk=
|
||||||
|
modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
|
||||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
@@ -1505,10 +1500,12 @@ modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
|||||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/sqlite v1.14.5 h1:bYrrjwH9Y7QUGk1MbchZDhRfmpGuEAs/D45sVjNbfvs=
|
|
||||||
modernc.org/sqlite v1.14.5/go.mod h1:YyX5Rx0WbXokitdWl2GJIDy4BrPxBP0PwwhpXOHCDLE=
|
modernc.org/sqlite v1.14.5/go.mod h1:YyX5Rx0WbXokitdWl2GJIDy4BrPxBP0PwwhpXOHCDLE=
|
||||||
|
modernc.org/sqlite v1.14.7 h1:A+6rGjtRQbt9SORXfV+hUyXOP3mDf7J5uz+EES/CNPE=
|
||||||
|
modernc.org/sqlite v1.14.7/go.mod h1:yiCvMv3HblGmzENNIaNtFhfaNIwcla4u2JQEwJPzfEc=
|
||||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||||
modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
|
modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
|
||||||
|
modernc.org/tcl v1.11.0/go.mod h1:zsTUpbQ+NxQEjOjCUlImDLPv1sG8Ww0qp66ZvyOxCgw=
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
|
modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
|
||||||
modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
|
modernc.org/z v1.3.0/go.mod h1:+mvgLH814oDjtATDdT3rs84JnUIpkvAF5B8AVkNlE2g=
|
||||||
@@ -1517,5 +1514,5 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||||
tailscale.com v1.20.4 h1:7cl/Q2Sbo2Jb2dX7zA+Exbbl7DT5UGZ4iGhQ2xj23X0=
|
tailscale.com v1.22.0 h1:/a1f6eKEl9vL/wGFP8mkhe7O1zDRGtWa9Ft2rGp5N80=
|
||||||
tailscale.com v1.20.4/go.mod h1:kjVy3ji2OH5lZhPLIIRacoY3CN4Bo3Yyb2mtoM8nfJ4=
|
tailscale.com v1.22.0/go.mod h1:D2zuDnjHT7v4aCt71c4+ytQUUAGpnypW+DoubYLaHjg=
|
||||||
|
39
grpcv1.go
39
grpcv1.go
@@ -3,12 +3,10 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/juanfont/headscale/gen/go/headscale/v1"
|
"github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/datatypes"
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,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
|
||||||
@@ -262,13 +262,8 @@ func (api headscaleV1APIServer) GetMachineRoute(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := machine.RoutesToProto()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v1.GetMachineRouteResponse{
|
return &v1.GetMachineRouteResponse{
|
||||||
Routes: routes,
|
Routes: machine.RoutesToProto(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,13 +281,8 @@ func (api headscaleV1APIServer) EnableMachineRoutes(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := machine.RoutesToProto()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v1.EnableMachineRoutesResponse{
|
return &v1.EnableMachineRoutesResponse{
|
||||||
Routes: routes,
|
Routes: machine.RoutesToProto(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,13 +369,6 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
|||||||
Hostname: "DebugTestMachine",
|
Hostname: "DebugTestMachine",
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Caller().Interface("hostinfo", hostinfo).Msg("")
|
|
||||||
|
|
||||||
hostinfoJson, err := json.Marshal(hostinfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMachine := Machine{
|
newMachine := Machine{
|
||||||
MachineKey: request.GetKey(),
|
MachineKey: request.GetKey(),
|
||||||
Name: request.GetName(),
|
Name: request.GetName(),
|
||||||
@@ -395,14 +378,14 @@ func (api headscaleV1APIServer) DebugCreateMachine(
|
|||||||
LastSeen: &time.Time{},
|
LastSeen: &time.Time{},
|
||||||
LastSuccessfulUpdate: &time.Time{},
|
LastSuccessfulUpdate: &time.Time{},
|
||||||
|
|
||||||
HostInfo: datatypes.JSON(hostinfoJson),
|
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,
|
||||||
|
@@ -6,6 +6,7 @@ package headscale
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
@@ -18,8 +19,15 @@ const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
|||||||
var (
|
var (
|
||||||
IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
|
IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
|
||||||
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
||||||
|
|
||||||
|
tailscaleVersions = []string{"1.22.0", "1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TestNamespace struct {
|
||||||
|
count int
|
||||||
|
tailscales map[string]dockertest.Resource
|
||||||
|
}
|
||||||
|
|
||||||
type ExecuteCommandConfig struct {
|
type ExecuteCommandConfig struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
@@ -119,3 +127,35 @@ func DockerAllowNetworkAdministration(config *docker.HostConfig) {
|
|||||||
Target: "/dev/net/tun",
|
Target: "/dev/net/tun",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getIPs(
|
||||||
|
tailscales map[string]dockertest.Resource,
|
||||||
|
) (map[string][]netaddr.IP, error) {
|
||||||
|
ips := make(map[string][]netaddr.IP)
|
||||||
|
for hostname, tailscale := range tailscales {
|
||||||
|
command := []string{"tailscale", "ip"}
|
||||||
|
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, address := range strings.Split(result, "\n") {
|
||||||
|
address = strings.TrimSuffix(address, "\n")
|
||||||
|
if len(address) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip, err := netaddr.ParseIP(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips[hostname] = append(ips[hostname], ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
396
integration_embedded_derp_test.go
Normal file
396
integration_embedded_derp_test.go
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/ory/dockertest/v3"
|
||||||
|
"github.com/ory/dockertest/v3/docker"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/ccding/go-stun/stun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
headscaleHostname = "headscale-derp"
|
||||||
|
namespaceName = "derpnamespace"
|
||||||
|
totalContainers = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntegrationDERPTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
stats *suite.SuiteInformation
|
||||||
|
|
||||||
|
pool dockertest.Pool
|
||||||
|
networks map[int]dockertest.Network // so we keep the containers isolated
|
||||||
|
headscale dockertest.Resource
|
||||||
|
|
||||||
|
tailscales map[string]dockertest.Resource
|
||||||
|
joinWaitGroup sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDERPIntegrationTestSuite(t *testing.T) {
|
||||||
|
s := new(IntegrationDERPTestSuite)
|
||||||
|
|
||||||
|
s.tailscales = make(map[string]dockertest.Resource)
|
||||||
|
s.networks = make(map[int]dockertest.Network)
|
||||||
|
|
||||||
|
suite.Run(t, s)
|
||||||
|
|
||||||
|
// HandleStats, which allows us to check if we passed and save logs
|
||||||
|
// is called after TearDown, so we cannot tear down containers before
|
||||||
|
// we have potentially saved the logs.
|
||||||
|
for _, tailscale := range s.tailscales {
|
||||||
|
if err := s.pool.Purge(&tailscale); err != nil {
|
||||||
|
log.Printf("Could not purge resource: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.stats.Passed() {
|
||||||
|
err := s.saveLog(&s.headscale, "test_output")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not save log: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.pool.Purge(&s.headscale); err != nil {
|
||||||
|
log.Printf("Could not purge resource: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range s.networks {
|
||||||
|
if err := network.Close(); err != nil {
|
||||||
|
log.Printf("Could not close network: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) SetupSuite() {
|
||||||
|
if ppool, err := dockertest.NewPool(""); err == nil {
|
||||||
|
s.pool = *ppool
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Could not connect to docker: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < totalContainers; i++ {
|
||||||
|
if pnetwork, err := s.pool.CreateNetwork(fmt.Sprintf("headscale-derp-%d", i)); err == nil {
|
||||||
|
s.networks[i] = *pnetwork
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Could not create network: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headscaleBuildOptions := &dockertest.BuildOptions{
|
||||||
|
Dockerfile: "Dockerfile",
|
||||||
|
ContextDir: ".",
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not determine current path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headscaleOptions := &dockertest.RunOptions{
|
||||||
|
Name: headscaleHostname,
|
||||||
|
Mounts: []string{
|
||||||
|
fmt.Sprintf("%s/integration_test/etc_embedded_derp:/etc/headscale", currentPath),
|
||||||
|
},
|
||||||
|
Cmd: []string{"headscale", "serve"},
|
||||||
|
ExposedPorts: []string{"8443/tcp", "3478/udp"},
|
||||||
|
PortBindings: map[docker.Port][]docker.PortBinding{
|
||||||
|
"8443/tcp": {{HostPort: "8443"}},
|
||||||
|
"3478/udp": {{HostPort: "3478"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Creating headscale container")
|
||||||
|
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
||||||
|
s.headscale = *pheadscale
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
|
}
|
||||||
|
log.Println("Created headscale container to test DERP")
|
||||||
|
|
||||||
|
log.Println("Creating tailscale containers")
|
||||||
|
|
||||||
|
for i := 0; i < totalContainers; i++ {
|
||||||
|
version := tailscaleVersions[i%len(tailscaleVersions)]
|
||||||
|
hostname, container := s.tailscaleContainer(
|
||||||
|
fmt.Sprint(i),
|
||||||
|
version,
|
||||||
|
s.networks[i],
|
||||||
|
)
|
||||||
|
s.tailscales[hostname] = *container
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Waiting for headscale to be ready")
|
||||||
|
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
|
||||||
|
|
||||||
|
if err := s.pool.Retry(func() error {
|
||||||
|
url := fmt.Sprintf("https://%s/health", hostEndpoint)
|
||||||
|
insecureTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
insecureTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
client := &http.Client{Transport: insecureTransport}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("status code not OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
// TODO(kradalby): If we cannot access headscale, or any other fatal error during
|
||||||
|
// test setup, we need to abort and tear down. However, testify does not seem to
|
||||||
|
// support that at the moment:
|
||||||
|
// https://github.com/stretchr/testify/issues/849
|
||||||
|
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
||||||
|
}
|
||||||
|
log.Println("headscale container is ready")
|
||||||
|
|
||||||
|
log.Printf("Creating headscale namespace: %s\n", namespaceName)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{"headscale", "namespaces", "create", namespaceName},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
log.Println("headscale create namespace result: ", result)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
log.Printf("Creating pre auth key for %s\n", namespaceName)
|
||||||
|
preAuthResult, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"--namespace",
|
||||||
|
namespaceName,
|
||||||
|
"preauthkeys",
|
||||||
|
"create",
|
||||||
|
"--reusable",
|
||||||
|
"--expiration",
|
||||||
|
"24h",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
[]string{"LOG_LEVEL=error"},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
var preAuthKey v1.PreAuthKey
|
||||||
|
err = json.Unmarshal([]byte(preAuthResult), &preAuthKey)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
assert.True(s.T(), preAuthKey.Reusable)
|
||||||
|
|
||||||
|
headscaleEndpoint := fmt.Sprintf("https://headscale:%s", s.headscale.GetPort("8443/tcp"))
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"Joining tailscale containers to headscale at %s\n",
|
||||||
|
headscaleEndpoint,
|
||||||
|
)
|
||||||
|
for hostname, tailscale := range s.tailscales {
|
||||||
|
s.joinWaitGroup.Add(1)
|
||||||
|
go s.Join(headscaleEndpoint, preAuthKey.Key, hostname, tailscale)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.joinWaitGroup.Wait()
|
||||||
|
|
||||||
|
// The nodes need a bit of time to get their updated maps from headscale
|
||||||
|
// TODO: See if we can have a more deterministic wait here.
|
||||||
|
time.Sleep(60 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) Join(
|
||||||
|
endpoint, key, hostname string,
|
||||||
|
tailscale dockertest.Resource,
|
||||||
|
) {
|
||||||
|
defer s.joinWaitGroup.Done()
|
||||||
|
|
||||||
|
command := []string{
|
||||||
|
"tailscale",
|
||||||
|
"up",
|
||||||
|
"-login-server",
|
||||||
|
endpoint,
|
||||||
|
"--authkey",
|
||||||
|
key,
|
||||||
|
"--hostname",
|
||||||
|
hostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Join command:", command)
|
||||||
|
log.Printf("Running join command for %s\n", hostname)
|
||||||
|
_, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
log.Printf("%s joined\n", hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) tailscaleContainer(identifier, version string, network dockertest.Network,
|
||||||
|
) (string, *dockertest.Resource) {
|
||||||
|
tailscaleBuildOptions := &dockertest.BuildOptions{
|
||||||
|
Dockerfile: "Dockerfile.tailscale",
|
||||||
|
ContextDir: ".",
|
||||||
|
BuildArgs: []docker.BuildArg{
|
||||||
|
{
|
||||||
|
Name: "TAILSCALE_VERSION",
|
||||||
|
Value: version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hostname := fmt.Sprintf(
|
||||||
|
"tailscale-%s-%s",
|
||||||
|
strings.Replace(version, ".", "-", -1),
|
||||||
|
identifier,
|
||||||
|
)
|
||||||
|
tailscaleOptions := &dockertest.RunOptions{
|
||||||
|
Name: hostname,
|
||||||
|
Networks: []*dockertest.Network{&network},
|
||||||
|
Cmd: []string{
|
||||||
|
"tailscaled", "--tun=tsdev",
|
||||||
|
},
|
||||||
|
|
||||||
|
// expose the host IP address, so we can access it from inside the container
|
||||||
|
ExtraHosts: []string{"host.docker.internal:host-gateway", "headscale:host-gateway"},
|
||||||
|
}
|
||||||
|
|
||||||
|
pts, err := s.pool.BuildAndRunWithBuildOptions(
|
||||||
|
tailscaleBuildOptions,
|
||||||
|
tailscaleOptions,
|
||||||
|
DockerRestartPolicy,
|
||||||
|
DockerAllowLocalIPv6,
|
||||||
|
DockerAllowNetworkAdministration,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("Created %s container\n", hostname)
|
||||||
|
|
||||||
|
return hostname, pts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) TearDownSuite() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) HandleStats(
|
||||||
|
suiteName string,
|
||||||
|
stats *suite.SuiteInformation,
|
||||||
|
) {
|
||||||
|
s.stats = stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) saveLog(
|
||||||
|
resource *dockertest.Resource,
|
||||||
|
basePath string,
|
||||||
|
) error {
|
||||||
|
err := os.MkdirAll(basePath, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
err = s.pool.Client.Logs(
|
||||||
|
docker.LogsOptions{
|
||||||
|
Context: context.TODO(),
|
||||||
|
Container: resource.Container.ID,
|
||||||
|
OutputStream: &stdout,
|
||||||
|
ErrorStream: &stderr,
|
||||||
|
Tail: "all",
|
||||||
|
RawTerminal: false,
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
Follow: false,
|
||||||
|
Timestamps: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stdout.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stderr.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) TestPingAllPeersByHostname() {
|
||||||
|
ips, err := getIPs(s.tailscales)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
for hostname, tailscale := range s.tailscales {
|
||||||
|
for peername := range ips {
|
||||||
|
if peername == hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||||
|
command := []string{
|
||||||
|
"tailscale", "ping",
|
||||||
|
"--timeout=10s",
|
||||||
|
"--c=5",
|
||||||
|
"--until-direct=false",
|
||||||
|
peername,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"Pinging using hostname from %s to %s\n",
|
||||||
|
hostname,
|
||||||
|
peername,
|
||||||
|
)
|
||||||
|
log.Println(command)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
log.Printf("Result for %s: %s\n", hostname, result)
|
||||||
|
assert.Contains(t, result, "via DERP(headscale)")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationDERPTestSuite) TestDERPSTUN() {
|
||||||
|
headscaleSTUNAddr := fmt.Sprintf("localhost:%s", s.headscale.GetPort("3478/udp"))
|
||||||
|
client := stun.NewClient()
|
||||||
|
client.SetVerbose(true)
|
||||||
|
client.SetVVerbose(true)
|
||||||
|
client.SetServerAddr(headscaleSTUNAddr)
|
||||||
|
_, _, err := client.Discover()
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
}
|
@@ -29,13 +29,6 @@ import (
|
|||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tailscaleVersions = []string{"1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
|
|
||||||
|
|
||||||
type TestNamespace struct {
|
|
||||||
count int
|
|
||||||
tailscales map[string]dockertest.Resource
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntegrationTestSuite struct {
|
type IntegrationTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
stats *suite.SuiteInformation
|
stats *suite.SuiteInformation
|
||||||
@@ -687,38 +680,6 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIPs(
|
|
||||||
tailscales map[string]dockertest.Resource,
|
|
||||||
) (map[string][]netaddr.IP, error) {
|
|
||||||
ips := make(map[string][]netaddr.IP)
|
|
||||||
for hostname, tailscale := range tailscales {
|
|
||||||
command := []string{"tailscale", "ip"}
|
|
||||||
|
|
||||||
result, err := ExecuteCommand(
|
|
||||||
&tailscale,
|
|
||||||
command,
|
|
||||||
[]string{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, address := range strings.Split(result, "\n") {
|
|
||||||
address = strings.TrimSuffix(address, "\n")
|
|
||||||
if len(address) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ip, err := netaddr.ParseIP(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ips[hostname] = append(ips[hostname], ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAPIURLs(
|
func getAPIURLs(
|
||||||
tailscales map[string]dockertest.Resource,
|
tailscales map[string]dockertest.Resource,
|
||||||
) (map[netaddr.IP]string, error) {
|
) (map[netaddr.IP]string, error) {
|
||||||
|
@@ -14,6 +14,7 @@ dns_config:
|
|||||||
db_path: /tmp/integration_test_db.sqlite3
|
db_path: /tmp/integration_test_db.sqlite3
|
||||||
private_key_path: private.key
|
private_key_path: private.key
|
||||||
listen_addr: 0.0.0.0:8080
|
listen_addr: 0.0.0.0:8080
|
||||||
|
metrics_listen_addr: 127.0.0.1:9090
|
||||||
server_url: http://headscale:8080
|
server_url: http://headscale:8080
|
||||||
|
|
||||||
derp:
|
derp:
|
||||||
|
28
integration_test/etc_embedded_derp/config.yaml
Normal file
28
integration_test/etc_embedded_derp/config.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
log_level: trace
|
||||||
|
acl_policy_path: ""
|
||||||
|
db_type: sqlite3
|
||||||
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
ip_prefixes:
|
||||||
|
- fd7a:115c:a1e0::/48
|
||||||
|
- 100.64.0.0/10
|
||||||
|
dns_config:
|
||||||
|
base_domain: headscale.net
|
||||||
|
magic_dns: true
|
||||||
|
domains: []
|
||||||
|
nameservers:
|
||||||
|
- 1.1.1.1
|
||||||
|
db_path: /tmp/integration_test_db.sqlite3
|
||||||
|
private_key_path: private.key
|
||||||
|
listen_addr: 0.0.0.0:8443
|
||||||
|
server_url: https://headscale:8443
|
||||||
|
tls_cert_path: "/etc/headscale/tls/server.crt"
|
||||||
|
tls_key_path: "/etc/headscale/tls/server.key"
|
||||||
|
tls_client_auth_mode: disabled
|
||||||
|
derp:
|
||||||
|
server:
|
||||||
|
enabled: true
|
||||||
|
region_id: 999
|
||||||
|
region_code: "headscale"
|
||||||
|
region_name: "Headscale Embedded DERP"
|
||||||
|
|
||||||
|
stun_listen_addr: "0.0.0.0:3478"
|
22
integration_test/etc_embedded_derp/tls/server.crt
Normal file
22
integration_test/etc_embedded_derp/tls/server.crt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC8jCCAdqgAwIBAgIULbu+UbSTMG/LtxooLLh7BgSEyqEwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFDESMBAGA1UEAwwJaGVhZHNjYWxlMCAXDTIyMDMwNTE2NDgwM1oYDzI1MjEx
|
||||||
|
MTA0MTY0ODAzWjAUMRIwEAYDVQQDDAloZWFkc2NhbGUwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQDqcfpToLZUF0rlNwXkkt3lbyw4Cl4TJdx36o2PKaOK
|
||||||
|
U+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1WATuQJlMeg+2UJXGaTGRKkkbPMy3
|
||||||
|
5m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6sXmNeETJvBixpBev9yKJuVXgqHNS4
|
||||||
|
NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH14rav8Uimonl8UTNVXufMzyUOuoaQ
|
||||||
|
TGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3uQJXy0m8I6OrIoXLNxnqYMfFls79
|
||||||
|
9SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJRG1E3ZiLAgMBAAGjOjA4MBQGA1Ud
|
||||||
|
EQQNMAuCCWhlYWRzY2FsZTALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||||
|
AwEwDQYJKoZIhvcNAQELBQADggEBANGlVN7NCsJaKz0k0nhlRGK+tcxn2p1PXN/i
|
||||||
|
Iy+JX8ahixPC4ocRwOhrXgb390ZXLLwq08HrWYRB/Wi1VUzCp5d8dVxvrR43dJ+v
|
||||||
|
L2EOBiIKgcu2C3pWW1qRR46/EoXUU9kSH2VNBvIhNufi32kEOidoDzxtQf6qVCoF
|
||||||
|
guUt1JkAqrynv1UvR/2ZRM/WzM/oJ8qfECwrwDxyYhkqU5Z5jCWg0C6kPIBvNdzt
|
||||||
|
B0eheWS+ZxVwkePTR4e17kIafwknth3lo+orxVrq/xC+OVM1bGrt2ZyD64ZvEqQl
|
||||||
|
w6kgbzBdLScAQptWOFThwhnJsg0UbYKimZsnYmjVEuN59TJv92M=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
(Expires on Nov 4 16:48:03 2521 GMT)
|
||||||
|
|
28
integration_test/etc_embedded_derp/tls/server.key
Normal file
28
integration_test/etc_embedded_derp/tls/server.key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqcfpToLZUF0rl
|
||||||
|
NwXkkt3lbyw4Cl4TJdx36o2PKaOKU+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1
|
||||||
|
WATuQJlMeg+2UJXGaTGRKkkbPMy35m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6s
|
||||||
|
XmNeETJvBixpBev9yKJuVXgqHNS4NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH1
|
||||||
|
4rav8Uimonl8UTNVXufMzyUOuoaQTGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3
|
||||||
|
uQJXy0m8I6OrIoXLNxnqYMfFls799SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJ
|
||||||
|
RG1E3ZiLAgMBAAECggEBALu1Ni/u5Qy++YA8ZcN0s6UXNdhItLmv/q0kZuLQ+9et
|
||||||
|
CT8VZfFInLndTdsaXenDKLHdryunviFA8SV+q7P2lMbek+Xs735EiyMnMBFWxLIZ
|
||||||
|
FWNGOeQERGL19QCmLEOmEi2b+iWJQHlKaMWpbPXL3w11a+lKjIBNO4ALfoJ5QveZ
|
||||||
|
cGMKsJdm/mpqBvLeNeh2eAFk3Gp6sT1g80Ge8NkgyzFBNIqnut0eerM15kPTc6Qz
|
||||||
|
12JLaOXUuV3PrcB4PN4nOwrTDg88GDNOQtc1Pc9r4nOHyLfr8X7QEtj1wXSwmOuK
|
||||||
|
d6ynMnAmoxVA9wEnupLbil1bzohRzpsTpkmDruYaBEECgYEA/Z09I8D6mt2NVqIE
|
||||||
|
KyvLjBK39ijSV9r3/lvB2Ple2OOL5YQEd+yTrIFy+3zdUnDgD1zmNnXjmjvHZ9Lc
|
||||||
|
IFf2o06AF84QLNB5gLPdDQkGNFdDqUxljBrfAfE3oANmPS/B0SijMGOOOiDO2FtO
|
||||||
|
xl1nfRr78mswuRs9awoUWCdNRKUCgYEA7KaTYKIQW/FEjw9lshp74q5vbn6zoXF5
|
||||||
|
7N8VkwI+bBVNvRbM9XZ8qhfgRdu9eXs5oL/N4mSYY54I8fA//pJ0Z2vpmureMm1V
|
||||||
|
mL5WBUmSD9DIbAchoK+sRiQhVmNMBQC6cHMABA7RfXvBeGvWrm9pKCS6ZLgLjkjp
|
||||||
|
PsmAcaXQcW8CgYEA2inAxljjOwUK6FNGsrxhxIT1qtNC3kCGxE+6WSNq67gSR8Vg
|
||||||
|
8qiX//T7LEslOB3RIGYRwxd2St7RkgZZRZllmOWWWuPwFhzf6E7RAL2akLvggGov
|
||||||
|
kG4tGEagSw2hjVDfsUT73ExHtMk0Jfmlsg33UC8+PDLpHtLH6qQpDAwC8+ECgYEA
|
||||||
|
o+AqOIWhvHmT11l7O915Ip1WzvZwYADbxLsrDnVEUsZh4epTHjvh0kvcY6PqTqCV
|
||||||
|
ZIrOANNWb811Nkz/k8NJVoD08PFp0xPBbZeIq/qpachTsfMyRzq/mobUiyUR9Hjv
|
||||||
|
ooUQYr78NOApNsG+lWbTNBhS9wI4BlzZIECbcJe5g4MCgYEAndRoy8S+S0Hx/S8a
|
||||||
|
O3hzXeDmivmgWqn8NVD4AKOovpkz4PaIVVQbAQkiNfAx8/DavPvjEKAbDezJ4ECV
|
||||||
|
j7IsOWtDVI7pd6eF9fTcECwisrda8aUoiOap8AQb48153Vx+g2N4Vy3uH0xJs4cz
|
||||||
|
TDALZPOBg8VlV+HEFDP43sp9Bf0=
|
||||||
|
-----END PRIVATE KEY-----
|
278
machine.go
278
machine.go
@@ -2,7 +2,6 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -13,7 +12,6 @@ import (
|
|||||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
"gorm.io/datatypes"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@@ -21,9 +19,12 @@ 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(
|
||||||
|
"machine not found in registration cache",
|
||||||
|
)
|
||||||
|
errCouldNotConvertMachineInterface = Error("failed to convert machine interface")
|
||||||
errHostnameTooLong = Error("Hostname too long")
|
errHostnameTooLong = Error("Hostname too long")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,8 +43,9 @@ type Machine struct {
|
|||||||
NamespaceID uint
|
NamespaceID uint
|
||||||
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
||||||
|
|
||||||
Registered bool // temp
|
|
||||||
RegisterMethod string
|
RegisterMethod string
|
||||||
|
|
||||||
|
// TODO(kradalby): This seems like irrelevant information?
|
||||||
AuthKeyID uint
|
AuthKeyID uint
|
||||||
AuthKey *PreAuthKey
|
AuthKey *PreAuthKey
|
||||||
|
|
||||||
@@ -51,9 +53,9 @@ type Machine struct {
|
|||||||
LastSuccessfulUpdate *time.Time
|
LastSuccessfulUpdate *time.Time
|
||||||
Expiry *time.Time
|
Expiry *time.Time
|
||||||
|
|
||||||
HostInfo datatypes.JSON
|
HostInfo HostInfo
|
||||||
Endpoints datatypes.JSON
|
Endpoints StringList
|
||||||
EnabledRoutes datatypes.JSON
|
EnabledRoutes IPPrefixes
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
@@ -65,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 {
|
||||||
@@ -116,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,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,
|
||||||
@@ -182,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,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")
|
||||||
|
|
||||||
@@ -277,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,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
|
||||||
}
|
}
|
||||||
@@ -393,20 +406,8 @@ func (h *Headscale) HardDeleteMachine(machine *Machine) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHostInfo returns a Hostinfo struct for the machine.
|
// GetHostInfo returns a Hostinfo struct for the machine.
|
||||||
func (machine *Machine) GetHostInfo() (*tailcfg.Hostinfo, error) {
|
func (machine *Machine) GetHostInfo() tailcfg.Hostinfo {
|
||||||
hostinfo := tailcfg.Hostinfo{}
|
return tailcfg.Hostinfo(machine.HostInfo)
|
||||||
if len(machine.HostInfo) != 0 {
|
|
||||||
hi, err := machine.HostInfo.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(hi, &hostinfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hostinfo, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) isOutdated(machine *Machine) bool {
|
func (h *Headscale) isOutdated(machine *Machine) bool {
|
||||||
@@ -536,54 +537,12 @@ func (machine Machine) toNode(
|
|||||||
// TODO(kradalby): Needs investigation, We probably dont need this condition
|
// TODO(kradalby): Needs investigation, We probably dont need this condition
|
||||||
// now that we dont have shared nodes
|
// now that we dont have shared nodes
|
||||||
if includeRoutes {
|
if includeRoutes {
|
||||||
routesStr := []string{}
|
allowedIPs = append(allowedIPs, machine.EnabledRoutes...)
|
||||||
if len(machine.EnabledRoutes) != 0 {
|
|
||||||
allwIps, err := machine.EnabledRoutes.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(allwIps, &routesStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, routeStr := range routesStr {
|
|
||||||
ip, err := netaddr.ParseIPPrefix(routeStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
allowedIPs = append(allowedIPs, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoints := []string{}
|
|
||||||
if len(machine.Endpoints) != 0 {
|
|
||||||
be, err := machine.Endpoints.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(be, &endpoints)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostinfo := tailcfg.Hostinfo{}
|
|
||||||
if len(machine.HostInfo) != 0 {
|
|
||||||
hi, err := machine.HostInfo.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(hi, &hostinfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var derp string
|
var derp string
|
||||||
if hostinfo.NetInfo != nil {
|
if machine.HostInfo.NetInfo != nil {
|
||||||
derp = fmt.Sprintf("127.3.3.40:%d", hostinfo.NetInfo.PreferredDERP)
|
derp = fmt.Sprintf("127.3.3.40:%d", machine.HostInfo.NetInfo.PreferredDERP)
|
||||||
} else {
|
} else {
|
||||||
derp = "127.3.3.40:0" // Zero means disconnected or unknown.
|
derp = "127.3.3.40:0" // Zero means disconnected or unknown.
|
||||||
}
|
}
|
||||||
@@ -614,6 +573,8 @@ func (machine Machine) toNode(
|
|||||||
hostname = machine.Name
|
hostname = machine.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostInfo := machine.GetHostInfo()
|
||||||
|
|
||||||
node := tailcfg.Node{
|
node := tailcfg.Node{
|
||||||
ID: tailcfg.NodeID(machine.ID), // this is the actual ID
|
ID: tailcfg.NodeID(machine.ID), // this is the actual ID
|
||||||
StableID: tailcfg.StableNodeID(
|
StableID: tailcfg.StableNodeID(
|
||||||
@@ -627,15 +588,15 @@ func (machine Machine) toNode(
|
|||||||
DiscoKey: discoKey,
|
DiscoKey: discoKey,
|
||||||
Addresses: addrs,
|
Addresses: addrs,
|
||||||
AllowedIPs: allowedIPs,
|
AllowedIPs: allowedIPs,
|
||||||
Endpoints: endpoints,
|
Endpoints: machine.Endpoints,
|
||||||
DERP: derp,
|
DERP: derp,
|
||||||
|
|
||||||
Hostinfo: hostinfo,
|
Hostinfo: hostInfo.View(),
|
||||||
Created: machine.CreatedAt,
|
Created: machine.CreatedAt,
|
||||||
LastSeen: machine.LastSeen,
|
LastSeen: machine.LastSeen,
|
||||||
|
|
||||||
KeepAlive: true,
|
KeepAlive: true,
|
||||||
MachineAuthorized: machine.Registered,
|
MachineAuthorized: !machine.isExpired(),
|
||||||
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
Capabilities: []string{tailcfg.CapabilityFileSharing},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,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: ,
|
||||||
|
|
||||||
@@ -682,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) {
|
||||||
|
if machineInterface, ok := h.registrationCache.Get(machineKeyStr); ok {
|
||||||
|
if registrationMachine, ok := machineInterface.(Machine); ok {
|
||||||
namespace, err := h.GetNamespace(namespaceName)
|
namespace, err := h.GetNamespace(namespaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
machine, err := h.RegisterMachine(
|
||||||
|
registrationMachine,
|
||||||
|
)
|
||||||
|
|
||||||
|
return machine, err
|
||||||
|
} else {
|
||||||
|
return nil, errCouldNotConvertMachineInterface
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, errMachineNotFoundRegistrationCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
||||||
|
func (h *Headscale) RegisterMachine(machine Machine,
|
||||||
|
) (*Machine, error) {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine_key_str", machineKeyStr).
|
Str("machine_key", machine.MachineKey).
|
||||||
Str("machine_key", machineKey.String()).
|
|
||||||
Msg("Registering machine")
|
Msg("Registering machine")
|
||||||
|
|
||||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(kradalby): Currently, if it fails to find a requested expiry, non will be set
|
|
||||||
// This means that if a user is to slow with register a machine, it will possibly not
|
|
||||||
// have the correct expiry.
|
|
||||||
requestedTime := time.Time{}
|
|
||||||
if requestedTimeIf, found := h.requestedExpiryCache.Get(machineKey.String()); found {
|
|
||||||
log.Trace().
|
|
||||||
Caller().
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Msg("Expiry time found in cache, assigning to node")
|
|
||||||
if reqTime, ok := requestedTimeIf.(time.Time); ok {
|
|
||||||
requestedTime = reqTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if machine.isRegistered() {
|
|
||||||
log.Trace().
|
|
||||||
Caller().
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Msg("machine already registered, reauthenticating")
|
|
||||||
|
|
||||||
h.RefreshMachine(machine, requestedTime)
|
|
||||||
|
|
||||||
return machine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
@@ -764,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().
|
||||||
@@ -783,40 +709,15 @@ 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, error) {
|
func (machine *Machine) GetAdvertisedRoutes() []netaddr.IPPrefix {
|
||||||
hostInfo, err := machine.GetHostInfo()
|
return machine.HostInfo.RoutableIPs
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostInfo.RoutableIPs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (machine *Machine) GetEnabledRoutes() ([]netaddr.IPPrefix, error) {
|
func (machine *Machine) GetEnabledRoutes() []netaddr.IPPrefix {
|
||||||
data, err := machine.EnabledRoutes.MarshalJSON()
|
return machine.EnabledRoutes
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
routesStr := []string{}
|
|
||||||
err = json.Unmarshal(data, &routesStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
routes := make([]netaddr.IPPrefix, len(routesStr))
|
|
||||||
for index, routeStr := range routesStr {
|
|
||||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
routes[index] = route
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
|
func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
|
||||||
@@ -825,10 +726,7 @@ func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledRoutes, err := machine.GetEnabledRoutes()
|
enabledRoutes := machine.GetEnabledRoutes()
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, enabledRoute := range enabledRoutes {
|
for _, enabledRoute := range enabledRoutes {
|
||||||
if route == enabledRoute {
|
if route == enabledRoute {
|
||||||
@@ -852,13 +750,8 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
|
|||||||
newRoutes[index] = route
|
newRoutes[index] = route
|
||||||
}
|
}
|
||||||
|
|
||||||
availableRoutes, err := machine.GetAdvertisedRoutes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, newRoute := range newRoutes {
|
for _, newRoute := range newRoutes {
|
||||||
if !containsIPPrefix(availableRoutes, newRoute) {
|
if !containsIPPrefix(machine.GetAdvertisedRoutes(), newRoute) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"route (%s) is not available on node %s: %w",
|
"route (%s) is not available on node %s: %w",
|
||||||
machine.Name,
|
machine.Name,
|
||||||
@@ -867,30 +760,19 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := json.Marshal(newRoutes)
|
machine.EnabledRoutes = newRoutes
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
machine.EnabledRoutes = datatypes.JSON(routes)
|
|
||||||
h.db.Save(&machine)
|
h.db.Save(&machine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (machine *Machine) RoutesToProto() (*v1.Routes, error) {
|
func (machine *Machine) RoutesToProto() *v1.Routes {
|
||||||
availableRoutes, err := machine.GetAdvertisedRoutes()
|
availableRoutes := machine.GetAdvertisedRoutes()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledRoutes, err := machine.GetEnabledRoutes()
|
enabledRoutes := machine.GetEnabledRoutes()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v1.Routes{
|
return &v1.Routes{
|
||||||
AdvertisedRoutes: ipPrefixToString(availableRoutes),
|
AdvertisedRoutes: ipPrefixToString(availableRoutes),
|
||||||
EnabledRoutes: ipPrefixToString(enabledRoutes),
|
EnabledRoutes: ipPrefixToString(enabledRoutes),
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
228
machine_test.go
228
machine_test.go
@@ -29,16 +29,12 @@ 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),
|
||||||
}
|
}
|
||||||
app.db.Save(machine)
|
app.db.Save(machine)
|
||||||
|
|
||||||
machineFromDB, err := app.GetMachine("test", "testmachine")
|
_, err = app.GetMachine("test", "testmachine")
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
_, err = machineFromDB.GetHostInfo()
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,16 +55,12 @@ 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),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
machineByID, err := app.GetMachineByID(0)
|
_, err = app.GetMachineByID(0)
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
_, err = machineByID.GetHostInfo()
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,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),
|
||||||
}
|
}
|
||||||
@@ -105,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),
|
||||||
}
|
}
|
||||||
@@ -136,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),
|
||||||
}
|
}
|
||||||
@@ -146,9 +135,6 @@ func (s *Suite) TestListPeers(c *check.C) {
|
|||||||
machine0ByID, err := app.GetMachineByID(0)
|
machine0ByID, err := app.GetMachineByID(0)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = machine0ByID.GetHostInfo()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
peersOfMachine0, err := app.ListPeers(machine0ByID)
|
peersOfMachine0, err := app.ListPeers(machine0ByID)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
@@ -188,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),
|
||||||
}
|
}
|
||||||
@@ -219,9 +204,6 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
|||||||
c.Logf("Machine(%v), namespace: %v", testMachine.Name, testMachine.Namespace)
|
c.Logf("Machine(%v), namespace: %v", testMachine.Name, testMachine.Namespace)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = testMachine.GetHostInfo()
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
machines, err := app.ListMachines()
|
machines, err := app.ListMachines()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
@@ -258,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{},
|
||||||
@@ -296,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
|
||||||
@@ -443,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"},
|
||||||
},
|
},
|
||||||
@@ -456,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) {
|
||||||
|
@@ -41,7 +41,7 @@ type Namespace struct {
|
|||||||
// CreateNamespace creates a new Namespace. Returns error if could not be created
|
// CreateNamespace creates a new Namespace. Returns error if could not be created
|
||||||
// or another namespace already exists.
|
// or another namespace already exists.
|
||||||
func (h *Headscale) CreateNamespace(name string) (*Namespace, error) {
|
func (h *Headscale) CreateNamespace(name string) (*Namespace, error) {
|
||||||
err := CheckNamespaceName(name)
|
err := CheckForFQDNRules(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = CheckNamespaceName(newName)
|
err = CheckForFQDNRules(newName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ func (h *Headscale) ListNamespaces() ([]Namespace, error) {
|
|||||||
|
|
||||||
// ListMachinesInNamespace gets all the nodes in a given namespace.
|
// ListMachinesInNamespace gets all the nodes in a given namespace.
|
||||||
func (h *Headscale) ListMachinesInNamespace(name string) ([]Machine, error) {
|
func (h *Headscale) ListMachinesInNamespace(name string) ([]Machine, error) {
|
||||||
err := CheckNamespaceName(name)
|
err := CheckForFQDNRules(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ func (h *Headscale) ListMachinesInNamespace(name string) ([]Machine, error) {
|
|||||||
|
|
||||||
// SetMachineNamespace assigns a Machine to a namespace.
|
// SetMachineNamespace assigns a Machine to a namespace.
|
||||||
func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string) error {
|
func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string) error {
|
||||||
err := CheckNamespaceName(namespaceName)
|
err := CheckForFQDNRules(namespaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -237,9 +237,9 @@ func (n *Namespace) toProto() *v1.Namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeNamespaceName will replace forbidden chars in namespace
|
// NormalizeToFQDNRules will replace forbidden chars in namespace
|
||||||
// it can also return an error if the namespace doesn't respect RFC 952 and 1123.
|
// it can also return an error if the namespace doesn't respect RFC 952 and 1123.
|
||||||
func NormalizeNamespaceName(name string, stripEmailDomain bool) (string, error) {
|
func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) {
|
||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
name = strings.ReplaceAll(name, "'", "")
|
name = strings.ReplaceAll(name, "'", "")
|
||||||
atIdx := strings.Index(name, "@")
|
atIdx := strings.Index(name, "@")
|
||||||
@@ -263,7 +263,7 @@ func NormalizeNamespaceName(name string, stripEmailDomain bool) (string, error)
|
|||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckNamespaceName(name string) error {
|
func CheckForFQDNRules(name string) error {
|
||||||
if len(name) > labelHostnameLength {
|
if len(name) > labelHostnameLength {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Namespace must not be over 63 chars. %v doesn't comply with this rule: %w",
|
"Namespace must not be over 63 chars. %v doesn't comply with this rule: %w",
|
||||||
|
@@ -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),
|
||||||
@@ -238,7 +233,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||||||
c.Assert(found, check.Equals, true)
|
c.Assert(found, check.Equals, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeNamespaceName(t *testing.T) {
|
func TestNormalizeToFQDNRules(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
name string
|
name string
|
||||||
stripEmailDomain bool
|
stripEmailDomain bool
|
||||||
@@ -315,10 +310,10 @@ func TestNormalizeNamespaceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := NormalizeNamespaceName(tt.args.name, tt.args.stripEmailDomain)
|
got, err := NormalizeToFQDNRules(tt.args.name, tt.args.stripEmailDomain)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"NormalizeNamespaceName() error = %v, wantErr %v",
|
"NormalizeToFQDNRules() error = %v, wantErr %v",
|
||||||
err,
|
err,
|
||||||
tt.wantErr,
|
tt.wantErr,
|
||||||
)
|
)
|
||||||
@@ -326,13 +321,13 @@ func TestNormalizeNamespaceName(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got != tt.want {
|
if got != tt.want {
|
||||||
t.Errorf("NormalizeNamespaceName() = %v, want %v", got, tt.want)
|
t.Errorf("NormalizeToFQDNRules() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckNamespaceName(t *testing.T) {
|
func TestCheckForFQDNRules(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
@@ -371,8 +366,8 @@ func TestCheckNamespaceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := CheckNamespaceName(tt.args.name); (err != nil) != tt.wantErr {
|
if err := CheckForFQDNRules(tt.args.name); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("CheckNamespaceName() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("CheckForFQDNRules() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
81
oidc.go
81
oidc.go
@@ -10,20 +10,15 @@ 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
|
|
||||||
oidcStateCacheCleanupInterval = time.Minute * 10
|
|
||||||
randomByteSize = 16
|
randomByteSize = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -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,9 +253,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
namespaceName, err := NormalizeToFQDNRules(
|
||||||
|
|
||||||
namespaceName, err := NormalizeNamespaceName(
|
|
||||||
claims.Email,
|
claims.Email,
|
||||||
h.cfg.OIDC.StripEmaildomain,
|
h.cfg.OIDC.StripEmaildomain,
|
||||||
)
|
)
|
||||||
@@ -294,12 +266,12 @@ 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 {
|
if err != nil {
|
||||||
@@ -328,29 +300,26 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := h.getAvailableIPs()
|
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||||
|
|
||||||
|
_, err = h.RegisterMachineFromAuthCallback(
|
||||||
|
machineKeyStr,
|
||||||
|
namespace.Name,
|
||||||
|
RegisterMethodOIDC,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("could not get an IP from the pool")
|
Msg("could not register machine")
|
||||||
ctx.String(
|
ctx.String(
|
||||||
http.StatusInternalServerError,
|
http.StatusInternalServerError,
|
||||||
"could not get an IP from the pool",
|
"could not register machine",
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
machine.IPAddresses = ips
|
|
||||||
machine.NamespaceID = namespace.ID
|
|
||||||
machine.Registered = true
|
|
||||||
machine.RegisterMethod = RegisterMethodOIDC
|
|
||||||
machine.LastSuccessfulUpdate = &now
|
|
||||||
machine.Expiry = &requestedTime
|
|
||||||
h.db.Save(&machine)
|
|
||||||
}
|
|
||||||
|
|
||||||
var content bytes.Buffer
|
var content bytes.Buffer
|
||||||
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
|
if err := oidcCallbackTemplate.Execute(&content, oidcCallbackTemplateConfig{
|
||||||
User: claims.Email,
|
User: claims.Email,
|
||||||
|
@@ -4,32 +4,125 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
textTemplate "text/template"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppleMobileConfig shows a simple message in the browser to point to the CLI
|
// WindowsConfigMessage shows a simple message in the browser for how to configure the Windows Tailscale client.
|
||||||
// Listens in /register.
|
func (h *Headscale) WindowsConfigMessage(ctx *gin.Context) {
|
||||||
func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
|
winTemplate := template.Must(template.New("windows").Parse(`
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>headscale</h1>
|
||||||
|
<h2>Windows registry configuration</h2>
|
||||||
|
<p>
|
||||||
|
This page provides Windows registry information for the official Windows Tailscale client.
|
||||||
|
<p>
|
||||||
|
<p>
|
||||||
|
The registry file will configure Tailscale to use <code>{{.URL}}</code> as its control server.
|
||||||
|
<p>
|
||||||
|
<h3>Caution</h3>
|
||||||
|
<p>You should always download and inspect the registry file before installing it:</p>
|
||||||
|
<pre><code>curl {{.URL}}/windows/tailscale.reg</code></pre>
|
||||||
|
|
||||||
|
<h2>Installation</h2>
|
||||||
|
<p>Headscale can be set to the default server by running the registry file:</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="/windows/tailscale.reg" download="tailscale.reg">Windows registry file</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Download the registry file, then run it</li>
|
||||||
|
<li>Follow the prompts</li>
|
||||||
|
<li>Install and run the official windows Tailscale client</li>
|
||||||
|
<li>When the installation has finished, start Tailscale, and log in by clicking the icon in the system tray</li>
|
||||||
|
</ol>
|
||||||
|
<p>Or</p>
|
||||||
|
<p>Open command prompt with Administrator rights. Issue the following commands to add the required registry entries:</p>
|
||||||
|
<pre>
|
||||||
|
<code>REG ADD "HKLM\Software\Tailscale IPN" /v UnattendedMode /t REG_SZ /d always
|
||||||
|
REG ADD "HKLM\Software\Tailscale IPN" /v LoginURL /t REG_SZ /d "{{.URL}}"</code></pre>
|
||||||
|
<p>
|
||||||
|
Restart Tailscale and log in.
|
||||||
|
<p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
config := map[string]interface{}{
|
||||||
|
"URL": h.cfg.ServerURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload bytes.Buffer
|
||||||
|
if err := winTemplate.Execute(&payload, config); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "WindowsRegConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Windows index template")
|
||||||
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Windows index template"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsRegConfig generates and serves a .reg file configured with the Headscale server address.
|
||||||
|
func (h *Headscale) WindowsRegConfig(ctx *gin.Context) {
|
||||||
|
config := WindowsRegistryConfig{
|
||||||
|
URL: h.cfg.ServerURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
var content bytes.Buffer
|
||||||
|
if err := windowsRegTemplate.Execute(&content, config); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "WindowsRegConfig").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render Apple macOS template")
|
||||||
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Windows registry template"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"text/x-ms-regedit; charset=utf-8",
|
||||||
|
content.Bytes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppleConfigMessage shows a simple message in the browser to point the user to the iOS/MacOS profile and instructions for how to install it.
|
||||||
|
func (h *Headscale) AppleConfigMessage(ctx *gin.Context) {
|
||||||
appleTemplate := template.Must(template.New("apple").Parse(`
|
appleTemplate := template.Must(template.New("apple").Parse(`
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>Apple configuration profiles</h1>
|
<h1>headscale</h1>
|
||||||
|
<h2>Apple configuration profiles</h2>
|
||||||
<p>
|
<p>
|
||||||
This page provides <a href="https://support.apple.com/guide/mdm/mdm-overview-mdmbf9e668/web">configuration profiles</a> for the official Tailscale clients for <a href="https://apps.apple.com/us/app/tailscale/id1470499037?ls=1">iOS</a> and <a href="https://apps.apple.com/ca/app/tailscale/id1475387142?mt=12">macOS</a>.
|
This page provides <a href="https://support.apple.com/guide/mdm/mdm-overview-mdmbf9e668/web">configuration profiles</a> for the official Tailscale clients for <a href="https://apps.apple.com/us/app/tailscale/id1470499037?ls=1">iOS</a> and <a href="https://apps.apple.com/ca/app/tailscale/id1475387142?mt=12">macOS</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The profiles will configure Tailscale.app to use {{.Url}} as its control server.
|
The profiles will configure Tailscale.app to use <code>{{.URL}}</code> as its control server.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Caution</h3>
|
<h3>Caution</h3>
|
||||||
<p>You should always inspect the profile before installing it:</p>
|
<p>You should always download and inspect the profile before installing it:</p>
|
||||||
<!--
|
<!--
|
||||||
<p><code>curl {{.Url}}/apple/ios</code></p>
|
<pre><code>curl {{.URL}}/apple/ios</code></pre>
|
||||||
-->
|
-->
|
||||||
<p><code>curl {{.Url}}/apple/macos</code></p>
|
<pre><code>curl {{.URL}}/apple/macos</code></pre>
|
||||||
|
|
||||||
<h2>Profiles</h2>
|
<h2>Profiles</h2>
|
||||||
|
|
||||||
@@ -191,6 +284,10 @@ func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WindowsRegistryConfig struct {
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
type AppleMobileConfig struct {
|
type AppleMobileConfig struct {
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
URL string
|
URL string
|
||||||
@@ -202,8 +299,16 @@ type AppleMobilePlatformConfig struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonTemplate = template.Must(
|
var windowsRegTemplate = textTemplate.Must(
|
||||||
template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
textTemplate.New("windowsconfig").Parse(`Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\SOFTWARE\Tailscale IPN]
|
||||||
|
"UnattendedMode"="always"
|
||||||
|
"LoginURL"="{{.URL}}"
|
||||||
|
`))
|
||||||
|
|
||||||
|
var commonTemplate = textTemplate.Must(
|
||||||
|
textTemplate.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
@@ -229,7 +334,7 @@ var commonTemplate = template.Must(
|
|||||||
</plist>`),
|
</plist>`),
|
||||||
)
|
)
|
||||||
|
|
||||||
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
var iosTemplate = textTemplate.Must(textTemplate.New("iosTemplate").Parse(`
|
||||||
<dict>
|
<dict>
|
||||||
<key>PayloadType</key>
|
<key>PayloadType</key>
|
||||||
<string>io.tailscale.ipn.ios</string>
|
<string>io.tailscale.ipn.ios</string>
|
30
poll.go
30
poll.go
@@ -2,7 +2,6 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/datatypes"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@@ -85,12 +83,19 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Found machine in database")
|
Msg("Found machine in database")
|
||||||
|
|
||||||
hostinfo, err := json.Marshal(req.Hostinfo)
|
hname, err := NormalizeToFQDNRules(
|
||||||
|
req.Hostinfo.Hostname,
|
||||||
|
h.cfg.OIDC.StripEmaildomain,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("func", "handleAuthKey").
|
||||||
|
Str("hostinfo.name", req.Hostinfo.Hostname).
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
machine.Name = req.Hostinfo.Hostname
|
machine.Name = hname
|
||||||
machine.HostInfo = datatypes.JSON(hostinfo)
|
machine.HostInfo = HostInfo(*req.Hostinfo)
|
||||||
machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
|
machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
@@ -114,18 +119,7 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
// The intended use is for clients to discover the DERP map at start-up
|
// The intended use is for clients to discover the DERP map at start-up
|
||||||
// before their first real endpoint update.
|
// before their first real endpoint update.
|
||||||
if !req.ReadOnly {
|
if !req.ReadOnly {
|
||||||
endpoints, err := json.Marshal(req.Endpoints)
|
machine.Endpoints = req.Endpoints
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Str("func", "PollNetMapHandler").
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to mashal requested endpoints for the client")
|
|
||||||
ctx.String(http.StatusInternalServerError, ":(")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
machine.Endpoints = datatypes.JSON(endpoints)
|
|
||||||
machine.LastSeen = &now
|
machine.LastSeen = &now
|
||||||
}
|
}
|
||||||
h.db.Updates(machine)
|
h.db.Updates(machine)
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
39
routes.go
39
routes.go
@@ -1,9 +1,6 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,12 +20,7 @@ func (h *Headscale) GetAdvertisedNodeRoutes(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hostInfo, err := machine.GetHostInfo()
|
return &machine.HostInfo.RoutableIPs, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hostInfo.RoutableIPs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
@@ -43,27 +35,7 @@ func (h *Headscale) GetEnabledNodeRoutes(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := machine.EnabledRoutes.MarshalJSON()
|
return machine.EnabledRoutes, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
routesStr := []string{}
|
|
||||||
err = json.Unmarshal(data, &routesStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
routes := make([]netaddr.IPPrefix, len(routesStr))
|
|
||||||
for index, routeStr := range routesStr {
|
|
||||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
routes[index] = route
|
|
||||||
}
|
|
||||||
|
|
||||||
return routes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
@@ -135,12 +107,7 @@ func (h *Headscale) EnableNodeRoute(
|
|||||||
return errRouteIsNotAvailable
|
return errRouteIsNotAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := json.Marshal(enabledRoutes)
|
machine.EnabledRoutes = enabledRoutes
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
machine.EnabledRoutes = datatypes.JSON(routes)
|
|
||||||
h.db.Save(&machine)
|
h.db.Save(&machine)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
"gorm.io/datatypes"
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@@ -25,8 +22,6 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
|||||||
hostInfo := tailcfg.Hostinfo{
|
hostInfo := tailcfg.Hostinfo{
|
||||||
RoutableIPs: []netaddr.IPPrefix{route},
|
RoutableIPs: []netaddr.IPPrefix{route},
|
||||||
}
|
}
|
||||||
hostinfo, err := json.Marshal(hostInfo)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
@@ -35,10 +30,9 @@ 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: datatypes.JSON(hostinfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
@@ -79,8 +73,6 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
|||||||
hostInfo := tailcfg.Hostinfo{
|
hostInfo := tailcfg.Hostinfo{
|
||||||
RoutableIPs: []netaddr.IPPrefix{route, route2},
|
RoutableIPs: []netaddr.IPPrefix{route, route2},
|
||||||
}
|
}
|
||||||
hostinfo, err := json.Marshal(hostInfo)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
machine := Machine{
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
@@ -89,10 +81,9 @@ 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: datatypes.JSON(hostinfo),
|
HostInfo: HostInfo(hostInfo),
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
|
10
tests/acls/acl_policy_basic_wildcards.yaml
Normal file
10
tests/acls/acl_policy_basic_wildcards.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
Hosts:
|
||||||
|
host-1: 100.100.100.100/32
|
||||||
|
subnet-1: 100.100.101.100/24
|
||||||
|
ACLs:
|
||||||
|
- Action: accept
|
||||||
|
Users:
|
||||||
|
- "*"
|
||||||
|
Ports:
|
||||||
|
- host-1:*
|
8
utils.go
8
utils.go
@@ -196,10 +196,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
|
|||||||
var addressesSlices []string
|
var addressesSlices []string
|
||||||
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||||
|
|
||||||
log.Trace().
|
|
||||||
Strs("addresses", addressesSlices).
|
|
||||||
Msg("Got allocated ip addresses from databases")
|
|
||||||
|
|
||||||
var ips netaddr.IPSetBuilder
|
var ips netaddr.IPSetBuilder
|
||||||
for _, slice := range addressesSlices {
|
for _, slice := range addressesSlices {
|
||||||
var machineAddresses MachineAddresses
|
var machineAddresses MachineAddresses
|
||||||
@@ -216,10 +212,6 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().
|
|
||||||
Interface("addresses", ips).
|
|
||||||
Msg("Parsed ip addresses that has been allocated from databases")
|
|
||||||
|
|
||||||
ipSet, err := ips.IPSet()
|
ipSet, err := ips.IPSet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &netaddr.IPSet{}, fmt.Errorf(
|
return &netaddr.IPSet{}, fmt.Errorf(
|
||||||
|
@@ -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),
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user