mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-14 12:59:56 +02:00
Compare commits
200 Commits
v0.12.3
...
v0.14.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69cdfbb56f | ||
|
|
d971f0f0e6 | ||
|
|
650108c7c7 | ||
|
|
baae266db0 | ||
|
|
50af44bc2f | ||
|
|
211fe4034a | ||
|
|
25550f8866 | ||
|
|
4bbe0051f6 | ||
|
|
5ab62378ae | ||
|
|
f006860136 | ||
|
|
9c6ce02554 | ||
|
|
960412a335 | ||
|
|
ecb3ee6bfa | ||
|
|
5242025ab3 | ||
|
|
b3d0fb7a93 | ||
|
|
5e167cc00a | ||
|
|
d00251c63e | ||
|
|
4f9ece14c5 | ||
|
|
602291df61 | ||
|
|
5245f1accc | ||
|
|
91babb5130 | ||
|
|
8798efd353 | ||
|
|
74621e2750 | ||
|
|
74c3c6bb60 | ||
|
|
84b98e716a | ||
|
|
e9f13b6031 | ||
|
|
a6b7bc5939 | ||
|
|
7d5e6d3f0f | ||
|
|
7a90c2fba1 | ||
|
|
5cf215a44b | ||
|
|
7916fa8b45 | ||
|
|
5fbef07627 | ||
|
|
21df798f07 | ||
|
|
67bb1fc9dd | ||
|
|
61bfa79be2 | ||
|
|
f073d8f43c | ||
|
|
5f642eef76 | ||
|
|
d8c4c3163b | ||
|
|
9cedbbafd4 | ||
|
|
aceaba60f1 | ||
|
|
7b5ba9f781 | ||
|
|
de59946447 | ||
|
|
97eac3b938 | ||
|
|
fb45138fc1 | ||
|
|
e9949b4c70 | ||
|
|
e482dfeed4 | ||
|
|
9b7d657cbe | ||
|
|
73497382b7 | ||
|
|
b2b2954545 | ||
|
|
a3360b082f | ||
|
|
b721502147 | ||
|
|
1869bff4ba | ||
|
|
0b9dd19ec7 | ||
|
|
b2889bc355 | ||
|
|
28c824acaf | ||
|
|
57f1da6dca | ||
|
|
c9640b2f3e | ||
|
|
546b1e8a05 | ||
|
|
3b54a68f5c | ||
|
|
1b1aac18d2 | ||
|
|
f30ee3d2df | ||
|
|
9f80349471 | ||
|
|
14b23544e4 | ||
|
|
4e54796384 | ||
|
|
c3b68adfed | ||
|
|
0018a78d5a | ||
|
|
50f0270543 | ||
|
|
bb80b679bc | ||
|
|
6fa0903a8e | ||
|
|
2bc8051ae5 | ||
|
|
4841e16386 | ||
|
|
d79ccfc05a | ||
|
|
ead8b68a03 | ||
|
|
3bb4c28c9a | ||
|
|
2fbcc38f8f | ||
|
|
315ff9daf0 | ||
|
|
4078e75b50 | ||
|
|
58bfea4e64 | ||
|
|
e18078d7f8 | ||
|
|
c73b57e7dc | ||
|
|
531298fa59 | ||
|
|
30a2ccd975 | ||
|
|
59e48993f2 | ||
|
|
bfc6f6e0eb | ||
|
|
811d3d510c | ||
|
|
2aba37d2ef | ||
|
|
8853ccd5b4 | ||
|
|
c794f32f58 | ||
|
|
dd8bae8c61 | ||
|
|
1b47ddd583 | ||
|
|
20991d6883 | ||
|
|
96f09e3f30 | ||
|
|
8f40696f35 | ||
|
|
c1845477ef | ||
|
|
1d40de3095 | ||
|
|
2357fb6f80 | ||
|
|
ba8afdb7be | ||
|
|
d9aaa0bdfc | ||
|
|
66ff34c2dd | ||
|
|
150652e939 | ||
|
|
8c79165b0d | ||
|
|
7b607b3fe8 | ||
|
|
41fbe47cdf | ||
|
|
e05c5e0b93 | ||
|
|
9e3318ca27 | ||
|
|
e9adfcd678 | ||
|
|
5b5ecd52e1 | ||
|
|
eddd62eee0 | ||
|
|
38c27f6bf8 | ||
|
|
90fb9aa4ed | ||
|
|
3af1253a65 | ||
|
|
eb1ce64b7c | ||
|
|
2c9ed63021 | ||
|
|
4c779d306b | ||
|
|
0862f60ff0 | ||
|
|
991175f2aa | ||
|
|
1815040d98 | ||
|
|
71ab4c9b2c | ||
|
|
e0c22a414b | ||
|
|
4e63bba4fe | ||
|
|
445c04baf7 | ||
|
|
ad4e3a89e0 | ||
|
|
6f6018bad5 | ||
|
|
ccd41b9a13 | ||
|
|
d8ce440309 | ||
|
|
2f576b2fb1 | ||
|
|
853a5288f1 | ||
|
|
cd0df1e46f | ||
|
|
b195c87418 | ||
|
|
45bcf39894 | ||
|
|
0a1db89d33 | ||
|
|
dbfb9e16e0 | ||
|
|
8aa2606853 | ||
|
|
a238a8b33a | ||
|
|
74f26d3685 | ||
|
|
e66f8b0eeb | ||
|
|
e7b69dbf91 | ||
|
|
13f23d2e7e | ||
|
|
7a86321252 | ||
|
|
7aace7eb6b | ||
|
|
7a6be36f46 | ||
|
|
bb27c80bad | ||
|
|
c0c3b7d511 | ||
|
|
6220836050 | ||
|
|
b122d06f12 | ||
|
|
6f9ed958ca | ||
|
|
39ce59fcb1 | ||
|
|
052fccdc98 | ||
|
|
17411b65f3 | ||
|
|
bf7ee78324 | ||
|
|
fbe5054a67 | ||
|
|
761147ea3b | ||
|
|
25ccf5ef18 | ||
|
|
b4f8961e44 | ||
|
|
726ccc8c1f | ||
|
|
126e694f26 | ||
|
|
ab45cd37f8 | ||
|
|
f59071ff1c | ||
|
|
537cd35cb2 | ||
|
|
56b6528e3b | ||
|
|
bae7ba46de | ||
|
|
fa197cc183 | ||
|
|
00c69ce50c | ||
|
|
a6e22387fd | ||
|
|
a730f007d8 | ||
|
|
3393363a67 | ||
|
|
8218ef96ef | ||
|
|
e8e573de62 | ||
|
|
05db1b7109 | ||
|
|
6e14fdf0d3 | ||
|
|
1fd57a3375 | ||
|
|
b4259fcd79 | ||
|
|
f9137f3bb0 | ||
|
|
b1a9b1ada1 | ||
|
|
b8e9024845 | ||
|
|
70d82ea184 | ||
|
|
9dc20580c7 | ||
|
|
4d60aeae18 | ||
|
|
67d1dd984f | ||
|
|
b02f8dd45d | ||
|
|
3837f1714a | ||
|
|
ed5498ef86 | ||
|
|
e2f8c69e2e | ||
|
|
beb3e9abc2 | ||
|
|
78039f4cea | ||
|
|
ed39b91f71 | ||
|
|
8f632e9062 | ||
|
|
a32175f791 | ||
|
|
d35fb8bba0 | ||
|
|
115d0cbe85 | ||
|
|
1a6e5d8770 | ||
|
|
3a3aecb774 | ||
|
|
8b40343277 | ||
|
|
7ec8346179 | ||
|
|
46cdce00af | ||
|
|
86f3f26a18 | ||
|
|
febbb6006f | ||
|
|
1d68509463 | ||
|
|
b6d0c4f2aa | ||
|
|
2c057c2d89 |
10
.github/CODEOWNERS
vendored
Normal file
10
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
* @juanfont @kradalby
|
||||||
|
|
||||||
|
*.md @ohdearaugustin
|
||||||
|
*.yml @ohdearaugustin
|
||||||
|
*.yaml @ohdearaugustin
|
||||||
|
Dockerfile* @ohdearaugustin
|
||||||
|
.goreleaser.yaml @ohdearaugustin
|
||||||
|
/docs/ @ohdearaugustin
|
||||||
|
/.github/workflows/ @ohdearaugustin
|
||||||
|
/.github/renovate.json @ohdearaugustin
|
||||||
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ko_fi: kradalby
|
||||||
|
github: [kradalby]
|
||||||
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -14,22 +14,38 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.3"
|
go-version: "1.17.7"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
go version
|
go version
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y make
|
sudo apt install -y make
|
||||||
|
|
||||||
- name: Run build
|
- name: Run build
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make build
|
run: make build
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
name: headscale-linux
|
name: headscale-linux
|
||||||
path: headscale
|
path: headscale
|
||||||
|
|||||||
4
.github/workflows/contributors.yml
vendored
4
.github/workflows/contributors.yml
vendored
@@ -4,13 +4,13 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
add-contributors:
|
add-contributors:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: BobAnkh/add-contributors@master
|
- uses: BobAnkh/add-contributors@v0.2.2
|
||||||
with:
|
with:
|
||||||
CONTRIBUTOR: "## Contributors"
|
CONTRIBUTOR: "## Contributors"
|
||||||
COLUMN_PER_ROW: "6"
|
COLUMN_PER_ROW: "6"
|
||||||
|
|||||||
37
.github/workflows/lint.yml
vendored
37
.github/workflows/lint.yml
vendored
@@ -8,18 +8,55 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
|
# Only block PRs on new problems.
|
||||||
|
# If this is not enabled, we will end up having PRs
|
||||||
|
# blocked because new linters has appared and other
|
||||||
|
# parts of the code is affected.
|
||||||
|
only-new-issues: true
|
||||||
|
|
||||||
prettier-lint:
|
prettier-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
**/*.md
|
||||||
|
**/*.yml
|
||||||
|
**/*.yaml
|
||||||
|
**/*.ts
|
||||||
|
**/*.js
|
||||||
|
**/*.sass
|
||||||
|
**/*.css
|
||||||
|
**/*.scss
|
||||||
|
**/*.html
|
||||||
|
|
||||||
- name: Prettify code
|
- name: Prettify code
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: creyD/prettier_action@v4.0
|
uses: creyD/prettier_action@v4.0
|
||||||
with:
|
with:
|
||||||
prettier_options: >-
|
prettier_options: >-
|
||||||
|
|||||||
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
|
go-version: 1.17.7
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
21
.github/workflows/test-integration.yml
vendored
21
.github/workflows/test-integration.yml
vendored
@@ -3,21 +3,30 @@ name: CI
|
|||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The "build" workflow
|
|
||||||
integration-test:
|
integration-test:
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
# Setup Go
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.3"
|
go-version: "1.17.7"
|
||||||
|
|
||||||
- name: Run Integration tests
|
- name: Run Integration tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: go test -tags integration -timeout 30m
|
run: go test -tags integration -timeout 30m
|
||||||
|
|||||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -3,31 +3,41 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The "build" workflow
|
|
||||||
test:
|
test:
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
# Setup Go
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.17.3" # The Go version to download (if necessary) and use.
|
go-version: "1.17.7"
|
||||||
|
|
||||||
# Install all the dependencies
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
go version
|
go version
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y make
|
sudo apt install -y make
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
- name: Run build
|
- name: Run build
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make
|
run: make
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ linters-settings:
|
|||||||
- ip
|
- ip
|
||||||
- ok
|
- ok
|
||||||
- c
|
- c
|
||||||
|
- tt
|
||||||
|
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# This is an example .goreleaser.yml file with some sane defaults.
|
---
|
||||||
# Make sure to check the documentation at http://goreleaser.com
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy -compat=1.17
|
||||||
|
|
||||||
release:
|
release:
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
@@ -33,7 +32,7 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- arm
|
- arm
|
||||||
goarm:
|
goarm:
|
||||||
- 7
|
- "7"
|
||||||
env:
|
env:
|
||||||
- CC=arm-linux-gnueabihf-gcc
|
- CC=arm-linux-gnueabihf-gcc
|
||||||
- CXX=arm-linux-gnueabihf-g++
|
- CXX=arm-linux-gnueabihf-g++
|
||||||
|
|||||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -2,6 +2,49 @@
|
|||||||
|
|
||||||
**TBD (TBD):**
|
**TBD (TBD):**
|
||||||
|
|
||||||
|
**0.14.0 (2022-xx-xx):**
|
||||||
|
|
||||||
|
**UPCOMING BREAKING**:
|
||||||
|
From the **next** version (`0.15.0`), all machines will be able to communicate regardless of
|
||||||
|
if they are in the same namespace. This means that the behaviour currently limited to ACLs
|
||||||
|
will become default. From version `0.15.0`, all limitation of communications must be done
|
||||||
|
with ACLs.
|
||||||
|
|
||||||
|
This is a part of aligning `headscale`'s behaviour with Tailscale's upstream behaviour.
|
||||||
|
|
||||||
|
**BREAKING**:
|
||||||
|
|
||||||
|
- 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
|
||||||
|
- All machines can communicate with all machines by default
|
||||||
|
- 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
|
||||||
|
|
||||||
|
**0.13.0 (2022-02-18):**
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Add IPv6 support to the prefix assigned to namespaces
|
||||||
|
- Add API Key support
|
||||||
|
- Enable remote control of `headscale` via CLI [docs](docs/remote-cli.md)
|
||||||
|
- Enable HTTP API (beta, subject to change)
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- `ip_prefix` is now superseded by `ip_prefixes` in the configuration [#208](https://github.com/juanfont/headscale/pull/208)
|
||||||
|
- Upgrade `tailscale` (1.20.4) and other dependencies to latest [#314](https://github.com/juanfont/headscale/pull/314)
|
||||||
|
- fix swapped machine<->namespace labels in `/metrics` [#312](https://github.com/juanfont/headscale/pull/312)
|
||||||
|
- remove key-value based update mechanism for namespace changes [#316](https://github.com/juanfont/headscale/pull/316)
|
||||||
|
|
||||||
|
**0.12.4 (2022-01-29):**
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
|
||||||
|
- Trim whitespace before reading Private Key from file [#289](https://github.com/juanfont/headscale/pull/289)
|
||||||
|
- Add new command to generate a private key for `headscale` [#290](https://github.com/juanfont/headscale/pull/290)
|
||||||
|
- Fixed issue where hosts deleted from control server may be written back to the database, as long as they are connected to the control server [#278](https://github.com/juanfont/headscale/pull/278)
|
||||||
|
|
||||||
**0.12.3 (2022-01-13):**
|
**0.12.3 (2022-01-13):**
|
||||||
|
|
||||||
**Changes**:
|
**Changes**:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM golang:1.17.1-bullseye AS build
|
FROM docker.io/golang:1.17.7-bullseye AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ RUN go mod download
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
||||||
|
RUN strip /go/bin/headscale
|
||||||
RUN test -e /go/bin/headscale
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
# Production image
|
# Production image
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM golang:1.17.1-alpine AS build
|
FROM docker.io/golang:1.17.7-alpine AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
@@ -10,10 +10,11 @@ RUN go mod download
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
||||||
|
RUN strip /go/bin/headscale
|
||||||
RUN test -e /go/bin/headscale
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
# Production image
|
# Production image
|
||||||
FROM alpine:latest
|
FROM docker.io/alpine:latest
|
||||||
|
|
||||||
COPY --from=build /go/bin/headscale /bin/headscale
|
COPY --from=build /go/bin/headscale /bin/headscale
|
||||||
ENV TZ UTC
|
ENV TZ UTC
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Builder image
|
# Builder image
|
||||||
FROM golang:1.17.1-bullseye AS build
|
FROM docker.io/golang:1.17.7-bullseye AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
WORKDIR /go/src/headscale
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ 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} \
|
&& apt-get install -y tailscale=${TAILSCALE_VERSION} dnsutils \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -18,7 +18,7 @@ test:
|
|||||||
@go test -coverprofile=coverage.out ./...
|
@go test -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
test_integration:
|
test_integration:
|
||||||
go test -tags integration -timeout 30m ./...
|
go test -tags integration -timeout 30m -count=1 ./...
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
199
README.md
199
README.md
@@ -10,7 +10,7 @@ Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat.
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
|
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using [NAT traversal](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||||
|
|
||||||
Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'.
|
Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'.
|
||||||
|
|
||||||
@@ -18,6 +18,12 @@ The control server works as an exchange point of Wireguard public keys for the n
|
|||||||
|
|
||||||
headscale implements this coordination server.
|
headscale implements this coordination server.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you like `headscale` and find it useful, there is sponsorship and donation buttons available in the repo.
|
||||||
|
|
||||||
|
If you would like to sponsor features, bugs or prioritisation, reach out to one of the maintainers.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- [x] Base functionality (nodes can communicate with each other)
|
- [x] Base functionality (nodes can communicate with each other)
|
||||||
@@ -42,11 +48,11 @@ headscale implements this coordination server.
|
|||||||
| Linux | Yes |
|
| Linux | Yes |
|
||||||
| OpenBSD | Yes |
|
| OpenBSD | Yes |
|
||||||
| macOS | Yes (see `/apple` on your headscale for more information) |
|
| macOS | Yes (see `/apple` on your headscale for more information) |
|
||||||
| Windows | Yes |
|
| Windows | Yes [docs](./docs/windows-client.md) |
|
||||||
| Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) |
|
| Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) |
|
||||||
| iOS | Not yet |
|
| iOS | Not yet |
|
||||||
|
|
||||||
## Roadmap 🤷
|
## Roadmap
|
||||||
|
|
||||||
Suggestions/PRs welcomed!
|
Suggestions/PRs welcomed!
|
||||||
|
|
||||||
@@ -65,7 +71,7 @@ To contribute to Headscale you would need the lastest version of [Go](https://go
|
|||||||
|
|
||||||
### Code style
|
### Code style
|
||||||
|
|
||||||
To ensure we have some consistency with a growing number of contributes, this project has adopted linting and style/formatting rules:
|
To ensure we have some consistency with a growing number of contributions, this project has adopted linting and style/formatting rules:
|
||||||
|
|
||||||
The **Go** code is linted with [`golangci-lint`](https://golangci-lint.run) and
|
The **Go** code is linted with [`golangci-lint`](https://golangci-lint.run) and
|
||||||
formatted with [`golines`](https://github.com/segmentio/golines) (width 88) and
|
formatted with [`golines`](https://github.com/segmentio/golines) (width 88) and
|
||||||
@@ -76,7 +82,7 @@ run `make lint` and `make fmt` before committing any code.
|
|||||||
The **Proto** code is linted with [`buf`](https://docs.buf.build/lint/overview) and
|
The **Proto** code is linted with [`buf`](https://docs.buf.build/lint/overview) and
|
||||||
formatted with [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
formatted with [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
||||||
|
|
||||||
The **rest** (markdown, yaml, etc) is formatted with [`prettier`](https://prettier.io).
|
The **rest** (Markdown, YAML, etc) is formatted with [`prettier`](https://prettier.io).
|
||||||
|
|
||||||
Check out the `.golangci.yaml` and `Makefile` to see the specific configuration.
|
Check out the `.golangci.yaml` and `Makefile` to see the specific configuration.
|
||||||
|
|
||||||
@@ -92,7 +98,7 @@ make install-protobuf-plugins
|
|||||||
|
|
||||||
### Testing and building
|
### Testing and building
|
||||||
|
|
||||||
Some parts of the project requires the generation of Go code from Protobuf (if changes is made in `proto/`) and it must be (re-)generated with:
|
Some parts of the project require the generation of Go code from Protobuf (if changes are made in `proto/`) and it must be (re-)generated with:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
make generate
|
make generate
|
||||||
@@ -116,13 +122,6 @@ make build
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
|
||||||
<a href=https://github.com/juanfont>
|
|
||||||
<img src=https://avatars.githubusercontent.com/u/181059?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Juan Font/>
|
|
||||||
<br />
|
|
||||||
<sub style="font-size:14px"><b>Juan Font</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/kradalby>
|
<a href=https://github.com/kradalby>
|
||||||
<img src=https://avatars.githubusercontent.com/u/98431?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Kristoffer Dalby/>
|
<img src=https://avatars.githubusercontent.com/u/98431?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Kristoffer Dalby/>
|
||||||
@@ -130,6 +129,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Kristoffer Dalby</b></sub>
|
<sub style="font-size:14px"><b>Kristoffer Dalby</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/juanfont>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/181059?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Juan Font/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Juan Font</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/>
|
||||||
@@ -144,6 +150,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>ohdearaugustin</b></sub>
|
<sub style="font-size:14px"><b>ohdearaugustin</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/ItalyPaleAle>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/43508?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Alessandro (Ale) Segala/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Alessandro (Ale) Segala</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/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/>
|
||||||
@@ -151,6 +164,15 @@ make build
|
|||||||
<sub style="font-size:14px"><b>unreality</b></sub>
|
<sub style="font-size:14px"><b>unreality</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Eugen Biegler</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/qbit>
|
<a href=https://github.com/qbit>
|
||||||
<img src=https://avatars.githubusercontent.com/u/68368?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Aaron Bieber/>
|
<img src=https://avatars.githubusercontent.com/u/68368?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Aaron Bieber/>
|
||||||
@@ -158,8 +180,27 @@ 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>
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<tr>
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Fernando De Lucchi</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/hdhoang>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/12537?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Hoàng Đức Hiếu/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Hoàng Đức Hiếu</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<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./>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Michael G.</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/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/>
|
||||||
@@ -167,6 +208,8 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Paul Tötterman</b></sub>
|
<sub style="font-size:14px"><b>Paul Tötterman</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/cmars>
|
<a href=https://github.com/cmars>
|
||||||
<img src=https://avatars.githubusercontent.com/u/23741?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Casey Marshall/>
|
<img src=https://avatars.githubusercontent.com/u/23741?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Casey Marshall/>
|
||||||
@@ -181,6 +224,20 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Silver Bullet</b></sub>
|
<sub style="font-size:14px"><b>Silver Bullet</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/majst01>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/410110?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Stefan Majer/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Stefan Majer</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/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=lachy-2849/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>lachy-2849</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/t56k>
|
<a href=https://github.com/t56k>
|
||||||
<img src=https://avatars.githubusercontent.com/u/12165422?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=thomas/>
|
<img src=https://avatars.githubusercontent.com/u/12165422?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=thomas/>
|
||||||
@@ -188,6 +245,29 @@ make build
|
|||||||
<sub style="font-size:14px"><b>thomas</b></sub>
|
<sub style="font-size:14px"><b>thomas</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/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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Abraham Ingersoll</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/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">
|
||||||
|
<a href=https://github.com/artemklevtsov>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/603798?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Artem Klevtsov/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Artem Klevtsov</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/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/>
|
||||||
@@ -195,6 +275,13 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Arthur Woimbée</b></sub>
|
<sub style="font-size:14px"><b>Arthur Woimbée</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/stensonb>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/933389?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Bryan Stenson/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Bryan Stenson</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/fkr>
|
<a href=https://github.com/fkr>
|
||||||
<img src=https://avatars.githubusercontent.com/u/51063?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Kronlage-Dammers/>
|
<img src=https://avatars.githubusercontent.com/u/51063?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Kronlage-Dammers/>
|
||||||
@@ -202,8 +289,6 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Felix Kronlage-Dammers</b></sub>
|
<sub style="font-size:14px"><b>Felix Kronlage-Dammers</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/felixonmars>
|
<a href=https://github.com/felixonmars>
|
||||||
<img src=https://avatars.githubusercontent.com/u/1006477?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Yan/>
|
<img src=https://avatars.githubusercontent.com/u/1006477?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Yan/>
|
||||||
@@ -211,6 +296,43 @@ 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">
|
||||||
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>JJGadgets</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Jim Tittsler</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/piec>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/781471?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Pierre Carru/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Pierre Carru</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/rcursaru>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/16259641?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=rcursaru/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>rcursaru</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Ryan Fowler</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/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/>
|
||||||
@@ -218,6 +340,15 @@ 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">
|
||||||
|
<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/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Tanner</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/Teteros>
|
<a href=https://github.com/Teteros>
|
||||||
<img src=https://avatars.githubusercontent.com/u/5067989?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Teteros/>
|
<img src=https://avatars.githubusercontent.com/u/5067989?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Teteros/>
|
||||||
@@ -246,8 +377,6 @@ 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>
|
||||||
</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/zekker6>
|
<a href=https://github.com/zekker6>
|
||||||
<img src=https://avatars.githubusercontent.com/u/1367798?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Zakhar Bessarab/>
|
<img src=https://avatars.githubusercontent.com/u/1367798?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Zakhar Bessarab/>
|
||||||
@@ -255,6 +384,15 @@ make build
|
|||||||
<sub style="font-size:14px"><b>Zakhar Bessarab</b></sub>
|
<sub style="font-size:14px"><b>Zakhar Bessarab</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/Bpazy>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/9838749?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=ZiYuan/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>ZiYuan</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/>
|
||||||
@@ -262,6 +400,13 @@ 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/>
|
||||||
@@ -269,6 +414,22 @@ make build
|
|||||||
<sub style="font-size:14px"><b>ignoramous</b></sub>
|
<sub style="font-size:14px"><b>ignoramous</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/lion24>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/1382102?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=lion24/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>lion24</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/Wakeful-Cloud>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/38930607?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Wakeful-Cloud/>
|
||||||
|
<br />
|
||||||
|
<sub style="font-size:14px"><b>Wakeful-Cloud</b></sub>
|
||||||
|
</a>
|
||||||
|
</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/xpzouying>
|
<a href=https://github.com/xpzouying>
|
||||||
<img src=https://avatars.githubusercontent.com/u/3946563?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=zy/>
|
<img src=https://avatars.githubusercontent.com/u/3946563?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=zy/>
|
||||||
|
|||||||
240
acls.go
240
acls.go
@@ -20,13 +20,15 @@ const (
|
|||||||
errInvalidUserSection = Error("invalid user section")
|
errInvalidUserSection = Error("invalid user section")
|
||||||
errInvalidGroup = Error("invalid group")
|
errInvalidGroup = Error("invalid group")
|
||||||
errInvalidTag = Error("invalid tag")
|
errInvalidTag = Error("invalid tag")
|
||||||
errInvalidNamespace = Error("invalid namespace")
|
|
||||||
errInvalidPortFormat = Error("invalid port format")
|
errInvalidPortFormat = Error("invalid port format")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
Base8 = 8
|
||||||
Base10 = 10
|
Base10 = 10
|
||||||
BitSize16 = 16
|
BitSize16 = 16
|
||||||
|
BitSize32 = 32
|
||||||
|
BitSize64 = 64
|
||||||
portRangeBegin = 0
|
portRangeBegin = 0
|
||||||
portRangeEnd = 65535
|
portRangeEnd = 65535
|
||||||
expectedTokenItems = 2
|
expectedTokenItems = 2
|
||||||
@@ -66,13 +68,17 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.aclPolicy = &policy
|
h.aclPolicy = &policy
|
||||||
|
|
||||||
|
return h.UpdateACLRules()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) UpdateACLRules() error {
|
||||||
rules, err := h.generateACLRules()
|
rules, err := h.generateACLRules()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.aclRules = rules
|
|
||||||
|
|
||||||
log.Trace().Interface("ACL", rules).Msg("ACL rules generated")
|
log.Trace().Interface("ACL", rules).Msg("ACL rules generated")
|
||||||
|
h.aclRules = rules
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -80,16 +86,23 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
|
func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
|
||||||
rules := []tailcfg.FilterRule{}
|
rules := []tailcfg.FilterRule{}
|
||||||
|
|
||||||
|
if h.aclPolicy == nil {
|
||||||
|
return nil, errEmptyPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
machines, err := h.ListAllMachines()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for index, acl := range h.aclPolicy.ACLs {
|
for index, acl := range h.aclPolicy.ACLs {
|
||||||
if acl.Action != "accept" {
|
if acl.Action != "accept" {
|
||||||
return nil, errInvalidAction
|
return nil, errInvalidAction
|
||||||
}
|
}
|
||||||
|
|
||||||
filterRule := tailcfg.FilterRule{}
|
|
||||||
|
|
||||||
srcIPs := []string{}
|
srcIPs := []string{}
|
||||||
for innerIndex, user := range acl.Users {
|
for innerIndex, user := range acl.Users {
|
||||||
srcs, err := h.generateACLPolicySrcIP(user)
|
srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Msgf("Error parsing ACL %d, User %d", index, innerIndex)
|
Msgf("Error parsing ACL %d, User %d", index, innerIndex)
|
||||||
@@ -98,11 +111,10 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
|
|||||||
}
|
}
|
||||||
srcIPs = append(srcIPs, srcs...)
|
srcIPs = append(srcIPs, srcs...)
|
||||||
}
|
}
|
||||||
filterRule.SrcIPs = srcIPs
|
|
||||||
|
|
||||||
destPorts := []tailcfg.NetPortRange{}
|
destPorts := []tailcfg.NetPortRange{}
|
||||||
for innerIndex, ports := range acl.Ports {
|
for innerIndex, ports := range acl.Ports {
|
||||||
dests, err := h.generateACLPolicyDestPorts(ports)
|
dests, err := h.generateACLPolicyDestPorts(machines, *h.aclPolicy, ports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
|
Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
|
||||||
@@ -121,11 +133,17 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicySrcIP(u string) ([]string, error) {
|
func (h *Headscale) generateACLPolicySrcIP(
|
||||||
return h.expandAlias(u)
|
machines []Machine,
|
||||||
|
aclPolicy ACLPolicy,
|
||||||
|
u string,
|
||||||
|
) ([]string, error) {
|
||||||
|
return expandAlias(machines, aclPolicy, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicyDestPorts(
|
func (h *Headscale) generateACLPolicyDestPorts(
|
||||||
|
machines []Machine,
|
||||||
|
aclPolicy ACLPolicy,
|
||||||
d string,
|
d string,
|
||||||
) ([]tailcfg.NetPortRange, error) {
|
) ([]tailcfg.NetPortRange, error) {
|
||||||
tokens := strings.Split(d, ":")
|
tokens := strings.Split(d, ":")
|
||||||
@@ -146,11 +164,11 @@ func (h *Headscale) generateACLPolicyDestPorts(
|
|||||||
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
|
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
expanded, err := h.expandAlias(alias)
|
expanded, err := expandAlias(machines, aclPolicy, alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ports, err := h.expandPorts(tokens[len(tokens)-1])
|
ports, err := expandPorts(tokens[len(tokens)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -169,23 +187,30 @@ func (h *Headscale) generateACLPolicyDestPorts(
|
|||||||
return dests, nil
|
return dests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) expandAlias(alias string) ([]string, error) {
|
// expandalias has an input of either
|
||||||
|
// - a namespace
|
||||||
|
// - a group
|
||||||
|
// - a tag
|
||||||
|
// and transform these in IPAddresses.
|
||||||
|
func expandAlias(
|
||||||
|
machines []Machine,
|
||||||
|
aclPolicy ACLPolicy,
|
||||||
|
alias string,
|
||||||
|
) ([]string, error) {
|
||||||
|
ips := []string{}
|
||||||
if alias == "*" {
|
if alias == "*" {
|
||||||
return []string{"*"}, nil
|
return []string{"*"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(alias, "group:") {
|
if strings.HasPrefix(alias, "group:") {
|
||||||
if _, ok := h.aclPolicy.Groups[alias]; !ok {
|
namespaces, err := expandGroup(aclPolicy, alias)
|
||||||
return nil, errInvalidGroup
|
if err != nil {
|
||||||
|
return ips, err
|
||||||
}
|
}
|
||||||
ips := []string{}
|
for _, n := range namespaces {
|
||||||
for _, n := range h.aclPolicy.Groups[alias] {
|
nodes := filterMachinesByNamespace(machines, n)
|
||||||
nodes, err := h.ListMachinesInNamespace(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errInvalidNamespace
|
|
||||||
}
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
ips = append(ips, node.IPAddress)
|
ips = append(ips, node.IPAddresses.ToStringSlice()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,35 +218,23 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(alias, "tag:") {
|
if strings.HasPrefix(alias, "tag:") {
|
||||||
if _, ok := h.aclPolicy.TagOwners[alias]; !ok {
|
owners, err := expandTagOwners(aclPolicy, alias)
|
||||||
return nil, errInvalidTag
|
if err != nil {
|
||||||
|
return ips, err
|
||||||
}
|
}
|
||||||
|
for _, namespace := range owners {
|
||||||
// This will have HORRIBLE performance.
|
machines := filterMachinesByNamespace(machines, namespace)
|
||||||
// We need to change the data model to better store tags
|
for _, machine := range machines {
|
||||||
machines := []Machine{}
|
if len(machine.HostInfo) == 0 {
|
||||||
if err := h.db.Where("registered").Find(&machines).Error; err != nil {
|
continue
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ips := []string{}
|
|
||||||
for _, machine := range machines {
|
|
||||||
hostinfo := tailcfg.Hostinfo{}
|
|
||||||
if len(machine.HostInfo) != 0 {
|
|
||||||
hi, err := machine.HostInfo.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(hi, &hostinfo)
|
hi, err := machine.GetHostInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ips, err
|
||||||
}
|
}
|
||||||
|
for _, t := range hi.RequestTags {
|
||||||
// FIXME: Check TagOwners allows this
|
if alias == t {
|
||||||
for _, t := range hostinfo.RequestTags {
|
ips = append(ips, machine.IPAddresses.ToStringSlice()...)
|
||||||
if alias[4:] == t {
|
|
||||||
ips = append(ips, machine.IPAddress)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,38 +243,82 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
|
|||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := h.GetNamespace(alias)
|
// if alias is a namespace
|
||||||
if err == nil {
|
nodes := filterMachinesByNamespace(machines, alias)
|
||||||
nodes, err := h.ListMachinesInNamespace(n.Name)
|
nodes, err := excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ips, err
|
||||||
}
|
}
|
||||||
ips := []string{}
|
for _, n := range nodes {
|
||||||
for _, n := range nodes {
|
ips = append(ips, n.IPAddresses.ToStringSlice()...)
|
||||||
ips = append(ips, n.IPAddress)
|
}
|
||||||
}
|
if len(ips) > 0 {
|
||||||
|
|
||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := h.aclPolicy.Hosts[alias]; ok {
|
// if alias is an host
|
||||||
|
if h, ok := aclPolicy.Hosts[alias]; ok {
|
||||||
return []string{h.String()}, nil
|
return []string{h.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if alias is an IP
|
||||||
ip, err := netaddr.ParseIP(alias)
|
ip, err := netaddr.ParseIP(alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return []string{ip.String()}, nil
|
return []string{ip.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if alias is an CIDR
|
||||||
cidr, err := netaddr.ParseIPPrefix(alias)
|
cidr, err := netaddr.ParseIPPrefix(alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return []string{cidr.String()}, nil
|
return []string{cidr.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errInvalidUserSection
|
return ips, errInvalidUserSection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
// excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones
|
||||||
|
// that are correctly tagged since they should not be listed as being in the namespace
|
||||||
|
// we assume in this function that we only have nodes from 1 namespace.
|
||||||
|
func excludeCorrectlyTaggedNodes(
|
||||||
|
aclPolicy ACLPolicy,
|
||||||
|
nodes []Machine,
|
||||||
|
namespace string,
|
||||||
|
) ([]Machine, error) {
|
||||||
|
out := []Machine{}
|
||||||
|
tags := []string{}
|
||||||
|
for tag, ns := range aclPolicy.TagOwners {
|
||||||
|
if containsString(ns, namespace) {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for each machine if tag is in tags list, don't append it.
|
||||||
|
for _, machine := range nodes {
|
||||||
|
if len(machine.HostInfo) == 0 {
|
||||||
|
out = append(out, machine)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hi, err := machine.GetHostInfo()
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, t := range hi.RequestTags {
|
||||||
|
if containsString(tags, t) {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
out = append(out, machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
||||||
if portsStr == "*" {
|
if portsStr == "*" {
|
||||||
return &[]tailcfg.PortRange{
|
return &[]tailcfg.PortRange{
|
||||||
{First: portRangeBegin, Last: portRangeEnd},
|
{First: portRangeBegin, Last: portRangeEnd},
|
||||||
@@ -303,3 +360,64 @@ func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
|||||||
|
|
||||||
return &ports, nil
|
return &ports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterMachinesByNamespace(machines []Machine, namespace string) []Machine {
|
||||||
|
out := []Machine{}
|
||||||
|
for _, machine := range machines {
|
||||||
|
if machine.Namespace.Name == namespace {
|
||||||
|
out = append(out, machine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
|
||||||
|
// a group cannot be composed of groups.
|
||||||
|
func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
|
||||||
|
var owners []string
|
||||||
|
ows, ok := aclPolicy.TagOwners[tag]
|
||||||
|
if !ok {
|
||||||
|
return []string{}, fmt.Errorf(
|
||||||
|
"%w. %v isn't owned by a TagOwner. Please add one first. https://tailscale.com/kb/1018/acls/#tag-owners",
|
||||||
|
errInvalidTag,
|
||||||
|
tag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, owner := range ows {
|
||||||
|
if strings.HasPrefix(owner, "group:") {
|
||||||
|
gs, err := expandGroup(aclPolicy, owner)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
owners = append(owners, gs...)
|
||||||
|
} else {
|
||||||
|
owners = append(owners, owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return owners, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expandGroup will return the list of namespace inside the group
|
||||||
|
// after some validation.
|
||||||
|
func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
|
||||||
|
groups, ok := aclPolicy.Groups[group]
|
||||||
|
if !ok {
|
||||||
|
return []string{}, fmt.Errorf(
|
||||||
|
"group %v isn't registered. %w",
|
||||||
|
group,
|
||||||
|
errInvalidGroup,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, g := range groups {
|
||||||
|
if strings.HasPrefix(g, "group:") {
|
||||||
|
return []string{}, fmt.Errorf(
|
||||||
|
"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
|
||||||
|
errInvalidGroup,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|||||||
1018
acls_test.go
1018
acls_test.go
File diff suppressed because it is too large
Load Diff
37
api.go
37
api.go
@@ -261,7 +261,16 @@ func (h *Headscale) getMapResponse(
|
|||||||
|
|
||||||
var respBody []byte
|
var respBody []byte
|
||||||
if req.Compress == "zstd" {
|
if req.Compress == "zstd" {
|
||||||
src, _ := json.Marshal(resp)
|
src, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("func", "getMapResponse").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to marshal response for the client")
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
encoder, _ := zstd.NewWriter(nil)
|
encoder, _ := zstd.NewWriter(nil)
|
||||||
srcCompressed := encoder.EncodeAll(src, nil)
|
srcCompressed := encoder.EncodeAll(src, nil)
|
||||||
@@ -290,7 +299,16 @@ func (h *Headscale) getMapKeepAliveResponse(
|
|||||||
var respBody []byte
|
var respBody []byte
|
||||||
var err error
|
var err error
|
||||||
if mapRequest.Compress == "zstd" {
|
if mapRequest.Compress == "zstd" {
|
||||||
src, _ := json.Marshal(mapResponse)
|
src, err := json.Marshal(mapResponse)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("func", "getMapKeepAliveResponse").
|
||||||
|
Err(err).
|
||||||
|
Msg("Failed to marshal keepalive response for the client")
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
encoder, _ := zstd.NewWriter(nil)
|
encoder, _ := zstd.NewWriter(nil)
|
||||||
srcCompressed := encoder.EncodeAll(src, nil)
|
srcCompressed := encoder.EncodeAll(src, nil)
|
||||||
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
||||||
@@ -497,6 +515,7 @@ func (h *Headscale) handleMachineRegistrationNew(
|
|||||||
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: check if any locks are needed around IP allocation.
|
||||||
func (h *Headscale) handleAuthKey(
|
func (h *Headscale) handleAuthKey(
|
||||||
ctx *gin.Context,
|
ctx *gin.Context,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
@@ -554,14 +573,14 @@ func (h *Headscale) handleAuthKey(
|
|||||||
log.Debug().
|
log.Debug().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Authentication key was valid, proceeding to acquire an IP address")
|
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||||
ip, err := h.getAvailableIP()
|
ips, err := h.getAvailableIPs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Failed to find an available IP")
|
Msg("Failed to find an available IP address")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
||||||
Inc()
|
Inc()
|
||||||
|
|
||||||
@@ -570,12 +589,12 @@ func (h *Headscale) handleAuthKey(
|
|||||||
log.Info().
|
log.Info().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", ip.String()).
|
Str("ips", strings.Join(ips.ToStringSlice(), ",")).
|
||||||
Msgf("Assigning %s to %s", ip, machine.Name)
|
Msgf("Assigning %s to %s", strings.Join(ips.ToStringSlice(), ","), machine.Name)
|
||||||
|
|
||||||
machine.Expiry = ®isterRequest.Expiry
|
machine.Expiry = ®isterRequest.Expiry
|
||||||
machine.AuthKeyID = uint(pak.ID)
|
machine.AuthKeyID = uint(pak.ID)
|
||||||
machine.IPAddress = ip.String()
|
machine.IPAddresses = ips
|
||||||
machine.NamespaceID = pak.NamespaceID
|
machine.NamespaceID = pak.NamespaceID
|
||||||
|
|
||||||
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
@@ -610,6 +629,6 @@ func (h *Headscale) handleAuthKey(
|
|||||||
log.Info().
|
log.Info().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", machine.IPAddress).
|
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
|
||||||
Msg("Successfully authenticated via AuthKey")
|
Msg("Successfully authenticated via AuthKey")
|
||||||
}
|
}
|
||||||
|
|||||||
164
api_key.go
Normal file
164
api_key.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apiPrefixLength = 7
|
||||||
|
apiKeyLength = 32
|
||||||
|
apiKeyParts = 2
|
||||||
|
|
||||||
|
errAPIKeyFailedToParse = Error("Failed to parse ApiKey")
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKey describes the datamodel for API keys used to remotely authenticate with
|
||||||
|
// headscale.
|
||||||
|
type APIKey struct {
|
||||||
|
ID uint64 `gorm:"primary_key"`
|
||||||
|
Prefix string `gorm:"uniqueIndex"`
|
||||||
|
Hash []byte
|
||||||
|
|
||||||
|
CreatedAt *time.Time
|
||||||
|
Expiration *time.Time
|
||||||
|
LastSeen *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAPIKey creates a new ApiKey in a namespace, and returns it.
|
||||||
|
func (h *Headscale) CreateAPIKey(
|
||||||
|
expiration *time.Time,
|
||||||
|
) (string, *APIKey, error) {
|
||||||
|
prefix, err := GenerateRandomStringURLSafe(apiPrefixLength)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeHashed, err := GenerateRandomStringURLSafe(apiKeyLength)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key to return to user, this will only be visible _once_
|
||||||
|
keyStr := prefix + "." + toBeHashed
|
||||||
|
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(toBeHashed), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := APIKey{
|
||||||
|
Prefix: prefix,
|
||||||
|
Hash: hash,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
h.db.Save(&key)
|
||||||
|
|
||||||
|
return keyStr, &key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAPIKeys returns the list of ApiKeys for a namespace.
|
||||||
|
func (h *Headscale) ListAPIKeys() ([]APIKey, error) {
|
||||||
|
keys := []APIKey{}
|
||||||
|
if err := h.db.Find(&keys).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKey returns a ApiKey for a given key.
|
||||||
|
func (h *Headscale) GetAPIKey(prefix string) (*APIKey, error) {
|
||||||
|
key := APIKey{}
|
||||||
|
if result := h.db.First(&key, "prefix = ?", prefix); result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAPIKeyByID returns a ApiKey for a given id.
|
||||||
|
func (h *Headscale) GetAPIKeyByID(id uint64) (*APIKey, error) {
|
||||||
|
key := APIKey{}
|
||||||
|
if result := h.db.Find(&APIKey{ID: id}).First(&key); result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return &key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyAPIKey destroys a ApiKey. Returns error if the ApiKey
|
||||||
|
// does not exist.
|
||||||
|
func (h *Headscale) DestroyAPIKey(key APIKey) error {
|
||||||
|
if result := h.db.Unscoped().Delete(key); result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpireAPIKey marks a ApiKey as expired.
|
||||||
|
func (h *Headscale) ExpireAPIKey(key *APIKey) error {
|
||||||
|
if err := h.db.Model(&key).Update("Expiration", time.Now()).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) ValidateAPIKey(keyStr string) (bool, error) {
|
||||||
|
prefix, hash, err := splitAPIKey(keyStr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to validate api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := h.GetAPIKey(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to validate api key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Expiration.Before(time.Now()) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword(key.Hash, []byte(hash)); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitAPIKey(key string) (string, string, error) {
|
||||||
|
parts := strings.Split(key, ".")
|
||||||
|
if len(parts) != apiKeyParts {
|
||||||
|
return "", "", errAPIKeyFailedToParse
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key *APIKey) toProto() *v1.ApiKey {
|
||||||
|
protoKey := v1.ApiKey{
|
||||||
|
Id: key.ID,
|
||||||
|
Prefix: key.Prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Expiration != nil {
|
||||||
|
protoKey.Expiration = timestamppb.New(*key.Expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.CreatedAt != nil {
|
||||||
|
protoKey.CreatedAt = timestamppb.New(*key.CreatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.LastSeen != nil {
|
||||||
|
protoKey.LastSeen = timestamppb.New(*key.LastSeen)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &protoKey
|
||||||
|
}
|
||||||
89
api_key_test.go
Normal file
89
api_key_test.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*Suite) TestCreateAPIKey(c *check.C) {
|
||||||
|
apiKeyStr, apiKey, err := app.CreateAPIKey(nil)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey, check.NotNil)
|
||||||
|
|
||||||
|
// Did we get a valid key?
|
||||||
|
c.Assert(apiKey.Prefix, check.NotNil)
|
||||||
|
c.Assert(apiKey.Hash, check.NotNil)
|
||||||
|
c.Assert(apiKeyStr, check.Not(check.Equals), "")
|
||||||
|
|
||||||
|
_, err = app.ListAPIKeys()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
keys, err := app.ListAPIKeys()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(len(keys), check.Equals, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Suite) TestAPIKeyDoesNotExist(c *check.C) {
|
||||||
|
key, err := app.GetAPIKey("does-not-exist")
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
c.Assert(key, check.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Suite) TestValidateAPIKeyOk(c *check.C) {
|
||||||
|
nowPlus2 := time.Now().Add(2 * time.Hour)
|
||||||
|
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey, check.NotNil)
|
||||||
|
|
||||||
|
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(valid, check.Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Suite) TestValidateAPIKeyNotOk(c *check.C) {
|
||||||
|
nowMinus2 := time.Now().Add(time.Duration(-2) * time.Hour)
|
||||||
|
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowMinus2)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey, check.NotNil)
|
||||||
|
|
||||||
|
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(valid, check.Equals, false)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
apiKeyStrNow, apiKey, err := app.CreateAPIKey(&now)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey, check.NotNil)
|
||||||
|
|
||||||
|
validNow, err := app.ValidateAPIKey(apiKeyStrNow)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(validNow, check.Equals, false)
|
||||||
|
|
||||||
|
validSilly, err := app.ValidateAPIKey("nota.validkey")
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
c.Assert(validSilly, check.Equals, false)
|
||||||
|
|
||||||
|
validWithErr, err := app.ValidateAPIKey("produceerrorkey")
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
c.Assert(validWithErr, check.Equals, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Suite) TestExpireAPIKey(c *check.C) {
|
||||||
|
nowPlus2 := time.Now().Add(2 * time.Hour)
|
||||||
|
apiKeyStr, apiKey, err := app.CreateAPIKey(&nowPlus2)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey, check.NotNil)
|
||||||
|
|
||||||
|
valid, err := app.ValidateAPIKey(apiKeyStr)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(valid, check.Equals, true)
|
||||||
|
|
||||||
|
err = app.ExpireAPIKey(apiKey)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(apiKey.Expiration, check.NotNil)
|
||||||
|
|
||||||
|
notValid, err := app.ValidateAPIKey(apiKeyStr)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(notValid, check.Equals, false)
|
||||||
|
}
|
||||||
389
app.go
389
app.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -26,7 +27,6 @@ import (
|
|||||||
zerolog "github.com/philip-bui/grpc-zerolog"
|
zerolog "github.com/philip-bui/grpc-zerolog"
|
||||||
zl "github.com/rs/zerolog"
|
zl "github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/soheilhy/cmux"
|
|
||||||
ginprometheus "github.com/zsais/go-gin-prometheus"
|
ginprometheus "github.com/zsais/go-gin-prometheus"
|
||||||
"golang.org/x/crypto/acme"
|
"golang.org/x/crypto/acme"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/peer"
|
"google.golang.org/grpc/peer"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
@@ -67,8 +68,10 @@ const (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
ServerURL string
|
ServerURL string
|
||||||
Addr string
|
Addr string
|
||||||
|
GRPCAddr string
|
||||||
|
GRPCAllowInsecure bool
|
||||||
EphemeralNodeInactivityTimeout time.Duration
|
EphemeralNodeInactivityTimeout time.Duration
|
||||||
IPPrefix netaddr.IPPrefix
|
IPPrefixes []netaddr.IPPrefix
|
||||||
PrivateKeyPath string
|
PrivateKeyPath string
|
||||||
BaseDomain string
|
BaseDomain string
|
||||||
|
|
||||||
@@ -95,7 +98,8 @@ type Config struct {
|
|||||||
|
|
||||||
DNSConfig *tailcfg.DNSConfig
|
DNSConfig *tailcfg.DNSConfig
|
||||||
|
|
||||||
UnixSocket string
|
UnixSocket string
|
||||||
|
UnixSocketPermission fs.FileMode
|
||||||
|
|
||||||
OIDC OIDCConfig
|
OIDC OIDCConfig
|
||||||
|
|
||||||
@@ -119,8 +123,8 @@ type DERPConfig struct {
|
|||||||
type CLIConfig struct {
|
type CLIConfig struct {
|
||||||
Address string
|
Address string
|
||||||
APIKey string
|
APIKey string
|
||||||
Insecure bool
|
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
Insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headscale represents the base app of the service.
|
// Headscale represents the base app of the service.
|
||||||
@@ -197,9 +201,7 @@ func NewHeadscale(cfg Config) (*Headscale, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
|
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
|
||||||
magicDNSDomains := generateMagicDNSRootDomains(
|
magicDNSDomains := generateMagicDNSRootDomains(app.cfg.IPPrefixes)
|
||||||
app.cfg.IPPrefix,
|
|
||||||
)
|
|
||||||
// we might have routes already from Split DNS
|
// we might have routes already from Split DNS
|
||||||
if app.cfg.DNSConfig.Routes == nil {
|
if app.cfg.DNSConfig.Routes == nil {
|
||||||
app.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver)
|
app.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver)
|
||||||
@@ -269,20 +271,6 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
|
|
||||||
// This is a way to communitate the CLI with the headscale server.
|
|
||||||
func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
|
|
||||||
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
|
||||||
for range ticker.C {
|
|
||||||
h.watchForKVUpdatesWorker()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Headscale) watchForKVUpdatesWorker() {
|
|
||||||
h.checkForNamespacesPendingUpdates()
|
|
||||||
// more functions will come here in the future
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
||||||
req interface{},
|
req interface{},
|
||||||
info *grpc.UnaryServerInfo,
|
info *grpc.UnaryServerInfo,
|
||||||
@@ -339,26 +327,26 @@ func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kradalby): Implement API key backend:
|
valid, err := h.ValidateAPIKey(strings.TrimPrefix(token, AuthPrefix))
|
||||||
// - Table in the DB
|
if err != nil {
|
||||||
// - Key name
|
log.Error().
|
||||||
// - Encrypted
|
Caller().
|
||||||
// - Expiry
|
Err(err).
|
||||||
//
|
Str("client_address", client.Addr.String()).
|
||||||
// Currently all other than localhost traffic is unauthorized, this is intentional to allow
|
Msg("failed to validate token")
|
||||||
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
|
||||||
// and API key auth
|
|
||||||
return ctx, status.Error(
|
|
||||||
codes.Unauthenticated,
|
|
||||||
"Authentication is not implemented yet",
|
|
||||||
)
|
|
||||||
|
|
||||||
// if strings.TrimPrefix(token, AUTH_PREFIX) != a.Token {
|
return ctx, status.Error(codes.Internal, "failed to validate token")
|
||||||
// log.Error().Caller().Str("client_address", p.Addr.String()).Msg("invalid token")
|
}
|
||||||
// return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return handler(ctx, req)
|
if !valid {
|
||||||
|
log.Info().
|
||||||
|
Str("client_address", client.Addr.String()).
|
||||||
|
Msg("invalid token")
|
||||||
|
|
||||||
|
return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) {
|
func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) {
|
||||||
@@ -381,19 +369,30 @@ func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) {
|
|||||||
|
|
||||||
ctx.AbortWithStatus(http.StatusUnauthorized)
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
|
||||||
// TODO(kradalby): Implement API key backend
|
valid, err := h.ValidateAPIKey(strings.TrimPrefix(authHeader, AuthPrefix))
|
||||||
// Currently all traffic is unauthorized, this is intentional to allow
|
if err != nil {
|
||||||
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
log.Error().
|
||||||
// and API key auth
|
Caller().
|
||||||
//
|
Err(err).
|
||||||
// if strings.TrimPrefix(authHeader, AUTH_PREFIX) != a.Token {
|
Str("client_address", ctx.ClientIP()).
|
||||||
// log.Error().Caller().Str("client_address", c.ClientIP()).Msg("invalid token")
|
Msg("failed to validate token")
|
||||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error", "unauthorized"})
|
|
||||||
|
|
||||||
// return
|
ctx.AbortWithStatus(http.StatusInternalServerError)
|
||||||
// }
|
|
||||||
|
|
||||||
// c.Next()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
log.Info().
|
||||||
|
Str("client_address", ctx.ClientIP()).
|
||||||
|
Msg("invalid token")
|
||||||
|
|
||||||
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear
|
// ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear
|
||||||
@@ -407,78 +406,7 @@ func (h *Headscale) ensureUnixSocketIsAbsent() error {
|
|||||||
return os.Remove(h.cfg.UnixSocket)
|
return os.Remove(h.cfg.UnixSocket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve launches a GIN server with the Headscale API.
|
func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *gin.Engine {
|
||||||
func (h *Headscale) Serve() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err = h.ensureUnixSocketIsAbsent()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to remove old socket file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set up gRPC socket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle common process-killing signals so we can gracefully shut down:
|
|
||||||
sigc := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
|
||||||
go func(c chan os.Signal) {
|
|
||||||
// Wait for a SIGINT or SIGKILL:
|
|
||||||
sig := <-c
|
|
||||||
log.Printf("Caught signal %s: shutting down.", sig)
|
|
||||||
// Stop listening (and unlink the socket if unix type):
|
|
||||||
socketListener.Close()
|
|
||||||
// And we're done:
|
|
||||||
os.Exit(0)
|
|
||||||
}(sigc)
|
|
||||||
|
|
||||||
networkListener, err := net.Listen("tcp", h.cfg.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the cmux object that will multiplex 2 protocols on the same port.
|
|
||||||
// The two following listeners will be served on the same port below gracefully.
|
|
||||||
networkMutex := cmux.New(networkListener)
|
|
||||||
// Match gRPC requests here
|
|
||||||
grpcListener := networkMutex.MatchWithWriters(
|
|
||||||
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
|
|
||||||
cmux.HTTP2MatchHeaderFieldSendSettings(
|
|
||||||
"content-type",
|
|
||||||
"application/grpc+proto",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
// Otherwise match regular http requests.
|
|
||||||
httpListener := networkMutex.Match(cmux.Any())
|
|
||||||
|
|
||||||
grpcGatewayMux := runtime.NewServeMux()
|
|
||||||
|
|
||||||
// Make the grpc-gateway connect to grpc over socket
|
|
||||||
grpcGatewayConn, err := grpc.Dial(
|
|
||||||
h.cfg.UnixSocket,
|
|
||||||
[]grpc.DialOption{
|
|
||||||
grpc.WithInsecure(),
|
|
||||||
grpc.WithContextDialer(GrpcSocketDialer),
|
|
||||||
}...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the gRPC server over localhost to skip
|
|
||||||
// the authentication.
|
|
||||||
err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
prometheus := ginprometheus.NewPrometheus("gin")
|
prometheus := ginprometheus.NewPrometheus("gin")
|
||||||
@@ -502,11 +430,18 @@ func (h *Headscale) Serve() error {
|
|||||||
api := router.Group("/api")
|
api := router.Group("/api")
|
||||||
api.Use(h.httpAuthenticationMiddleware)
|
api.Use(h.httpAuthenticationMiddleware)
|
||||||
{
|
{
|
||||||
api.Any("/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP))
|
api.Any("/v1/*any", gin.WrapF(grpcMux.ServeHTTP))
|
||||||
}
|
}
|
||||||
|
|
||||||
router.NoRoute(stdoutHandler)
|
router.NoRoute(stdoutHandler)
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve launches a GIN server with the Headscale API.
|
||||||
|
func (h *Headscale) Serve() error {
|
||||||
|
var err 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)
|
||||||
|
|
||||||
@@ -516,10 +451,150 @@ func (h *Headscale) Serve() error {
|
|||||||
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// I HATE THIS
|
|
||||||
go h.watchForKVUpdates(updateInterval)
|
|
||||||
go h.expireEphemeralNodes(updateInterval)
|
go h.expireEphemeralNodes(updateInterval)
|
||||||
|
|
||||||
|
if zl.GlobalLevel() == zl.TraceLevel {
|
||||||
|
zerolog.RespLog = true
|
||||||
|
} else {
|
||||||
|
zerolog.RespLog = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare group for running listeners
|
||||||
|
errorGroup := new(errgroup.Group)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Set up LOCAL listeners
|
||||||
|
//
|
||||||
|
|
||||||
|
err = h.ensureUnixSocketIsAbsent()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to remove old socket file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set up gRPC socket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change socket permissions
|
||||||
|
if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil {
|
||||||
|
return fmt.Errorf("failed change permission of gRPC socket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle common process-killing signals so we can gracefully shut down:
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func(c chan os.Signal) {
|
||||||
|
// Wait for a SIGINT or SIGKILL:
|
||||||
|
sig := <-c
|
||||||
|
log.Printf("Caught signal %s: shutting down.", sig)
|
||||||
|
// Stop listening (and unlink the socket if unix type):
|
||||||
|
socketListener.Close()
|
||||||
|
// And we're done:
|
||||||
|
os.Exit(0)
|
||||||
|
}(sigc)
|
||||||
|
|
||||||
|
grpcGatewayMux := runtime.NewServeMux()
|
||||||
|
|
||||||
|
// Make the grpc-gateway connect to grpc over socket
|
||||||
|
grpcGatewayConn, err := grpc.Dial(
|
||||||
|
h.cfg.UnixSocket,
|
||||||
|
[]grpc.DialOption{
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithContextDialer(GrpcSocketDialer),
|
||||||
|
}...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the gRPC server over localhost to skip
|
||||||
|
// the authentication.
|
||||||
|
err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the local gRPC server without TLS and without authentication
|
||||||
|
grpcSocket := grpc.NewServer(zerolog.UnaryInterceptor())
|
||||||
|
|
||||||
|
v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
|
||||||
|
reflection.Register(grpcSocket)
|
||||||
|
|
||||||
|
errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) })
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Set up REMOTE listeners
|
||||||
|
//
|
||||||
|
|
||||||
|
tlsConfig, err := h.getTLSSettings()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to set up TLS configuration")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// gRPC setup
|
||||||
|
//
|
||||||
|
|
||||||
|
// We are sadly not able to run gRPC and HTTPS (2.0) on the same
|
||||||
|
// port because the connection mux does not support matching them
|
||||||
|
// since they are so similar. There is multiple issues open and we
|
||||||
|
// can revisit this if changes:
|
||||||
|
// https://github.com/soheilhy/cmux/issues/68
|
||||||
|
// https://github.com/soheilhy/cmux/issues/91
|
||||||
|
|
||||||
|
if tlsConfig != nil || h.cfg.GRPCAllowInsecure {
|
||||||
|
log.Info().Msgf("Enabling remote gRPC at %s", h.cfg.GRPCAddr)
|
||||||
|
|
||||||
|
grpcOptions := []grpc.ServerOption{
|
||||||
|
grpc.UnaryInterceptor(
|
||||||
|
grpc_middleware.ChainUnaryServer(
|
||||||
|
h.grpcAuthenticationInterceptor,
|
||||||
|
zerolog.NewUnaryServerInterceptor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig != nil {
|
||||||
|
grpcOptions = append(grpcOptions,
|
||||||
|
grpc.Creds(credentials.NewTLS(tlsConfig)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Warn().Msg("gRPC is running without security")
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer(grpcOptions...)
|
||||||
|
|
||||||
|
v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
|
||||||
|
grpcListener, err := net.Listen("tcp", h.cfg.GRPCAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) })
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Msgf("listening and serving gRPC on: %s", h.cfg.GRPCAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// HTTP setup
|
||||||
|
//
|
||||||
|
|
||||||
|
router := h.createRouter(grpcGatewayMux)
|
||||||
|
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: h.cfg.Addr,
|
Addr: h.cfg.Addr,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
@@ -531,65 +606,21 @@ func (h *Headscale) Serve() error {
|
|||||||
WriteTimeout: 0,
|
WriteTimeout: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if zl.GlobalLevel() == zl.TraceLevel {
|
var httpListener net.Listener
|
||||||
zerolog.RespLog = true
|
|
||||||
} else {
|
|
||||||
zerolog.RespLog = false
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcOptions := []grpc.ServerOption{
|
|
||||||
grpc.UnaryInterceptor(
|
|
||||||
grpc_middleware.ChainUnaryServer(
|
|
||||||
h.grpcAuthenticationInterceptor,
|
|
||||||
zerolog.NewUnaryServerInterceptor(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig, err := h.getTLSSettings()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to set up TLS configuration")
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsConfig != nil {
|
if tlsConfig != nil {
|
||||||
httpServer.TLSConfig = tlsConfig
|
httpServer.TLSConfig = tlsConfig
|
||||||
|
httpListener, err = tls.Listen("tcp", h.cfg.Addr, tlsConfig)
|
||||||
grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
|
||||||
}
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(grpcOptions...)
|
|
||||||
|
|
||||||
// Start the local gRPC server without TLS and without authentication
|
|
||||||
grpcSocket := grpc.NewServer(zerolog.UnaryInterceptor())
|
|
||||||
|
|
||||||
v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
|
|
||||||
v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
|
|
||||||
reflection.Register(grpcServer)
|
|
||||||
reflection.Register(grpcSocket)
|
|
||||||
|
|
||||||
errorGroup := new(errgroup.Group)
|
|
||||||
|
|
||||||
errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) })
|
|
||||||
|
|
||||||
// TODO(kradalby): Verify if we need the same TLS setup for gRPC as HTTP
|
|
||||||
errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) })
|
|
||||||
|
|
||||||
if tlsConfig != nil {
|
|
||||||
errorGroup.Go(func() error {
|
|
||||||
tlsl := tls.NewListener(httpListener, tlsConfig)
|
|
||||||
|
|
||||||
return httpServer.Serve(tlsl)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
|
httpListener, err = net.Listen("tcp", h.cfg.Addr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errorGroup.Go(func() error { return networkMutex.Serve() })
|
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
|
||||||
|
|
||||||
log.Info().
|
log.Info().
|
||||||
Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr)
|
Msgf("listening and serving HTTP on: %s", h.cfg.Addr)
|
||||||
|
|
||||||
return errorGroup.Wait()
|
return errorGroup.Wait()
|
||||||
}
|
}
|
||||||
@@ -625,6 +656,7 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
|||||||
// service, which can be configured to run on any other port.
|
// service, which can be configured to run on any other port.
|
||||||
go func() {
|
go func() {
|
||||||
log.Fatal().
|
log.Fatal().
|
||||||
|
Caller().
|
||||||
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, certManager.HTTPHandler(http.HandlerFunc(h.redirect)))).
|
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, certManager.HTTPHandler(http.HandlerFunc(h.redirect)))).
|
||||||
Msg("failed to set up a HTTP server")
|
Msg("failed to set up a HTTP server")
|
||||||
}()
|
}()
|
||||||
@@ -724,7 +756,8 @@ func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
|
|||||||
return nil, fmt.Errorf("failed to read private key file: %w", err)
|
return nil, fmt.Errorf("failed to read private key file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeyEnsurePrefix := PrivateKeyEnsurePrefix(string(privateKey))
|
trimmedPrivateKey := strings.TrimSpace(string(privateKey))
|
||||||
|
privateKeyEnsurePrefix := PrivateKeyEnsurePrefix(trimmedPrivateKey)
|
||||||
|
|
||||||
var machineKey key.MachinePrivate
|
var machineKey key.MachinePrivate
|
||||||
if err = machineKey.UnmarshalText([]byte(privateKeyEnsurePrefix)); err != nil {
|
if err = machineKey.UnmarshalText([]byte(privateKeyEnsurePrefix)); err != nil {
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ func (s *Suite) ResetDB(c *check.C) {
|
|||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
cfg := Config{
|
cfg := Config{
|
||||||
IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"),
|
IPPrefixes: []netaddr.IPPrefix{
|
||||||
|
netaddr.MustParseIPPrefix("10.27.0.0/23"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app = Headscale{
|
app = Headscale{
|
||||||
|
|||||||
10
cli_test.go
10
cli_test.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestRegisterMachine(c *check.C) {
|
func (s *Suite) TestRegisterMachine(c *check.C) {
|
||||||
@@ -19,16 +20,17 @@ func (s *Suite) TestRegisterMachine(c *check.C) {
|
|||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
IPAddress: "10.0.0.1",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")},
|
||||||
Expiry: &now,
|
Expiry: &now,
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
err = app.db.Save(&machine).Error
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = app.GetMachine("test", "testmachine")
|
_, err = app.GetMachine(namespace.Name, machine.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
machineAfterRegistering, err := app.RegisterMachine(
|
machineAfterRegistering, err := app.RegisterMachine(
|
||||||
"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
|
machine.MachineKey,
|
||||||
namespace.Name,
|
namespace.Name,
|
||||||
)
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|||||||
183
cmd/headscale/cli/api_key.go
Normal file
183
cmd/headscale/cli/api_key.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/juanfont/headscale"
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 90 days.
|
||||||
|
DefaultAPIKeyExpiry = 90 * 24 * time.Hour
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(apiKeysCmd)
|
||||||
|
apiKeysCmd.AddCommand(listAPIKeys)
|
||||||
|
|
||||||
|
createAPIKeyCmd.Flags().
|
||||||
|
DurationP("expiration", "e", DefaultAPIKeyExpiry, "Human-readable expiration of the key (30m, 24h, 365d...)")
|
||||||
|
|
||||||
|
apiKeysCmd.AddCommand(createAPIKeyCmd)
|
||||||
|
|
||||||
|
expireAPIKeyCmd.Flags().StringP("prefix", "p", "", "ApiKey prefix")
|
||||||
|
err := expireAPIKeyCmd.MarkFlagRequired("prefix")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("")
|
||||||
|
}
|
||||||
|
apiKeysCmd.AddCommand(expireAPIKeyCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiKeysCmd = &cobra.Command{
|
||||||
|
Use: "apikeys",
|
||||||
|
Short: "Handle the Api keys in Headscale",
|
||||||
|
}
|
||||||
|
|
||||||
|
var listAPIKeys = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List the Api keys for headscale",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ListApiKeysRequest{}
|
||||||
|
|
||||||
|
response, err := client.ListApiKeys(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting the list of keys: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
SuccessOutput(response.ApiKeys, "", output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData := pterm.TableData{
|
||||||
|
{"ID", "Prefix", "Expiration", "Created"},
|
||||||
|
}
|
||||||
|
for _, key := range response.ApiKeys {
|
||||||
|
expiration := "-"
|
||||||
|
|
||||||
|
if key.GetExpiration() != nil {
|
||||||
|
expiration = ColourTime(key.Expiration.AsTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData = append(tableData, []string{
|
||||||
|
strconv.FormatUint(key.GetId(), headscale.Base10),
|
||||||
|
key.GetPrefix(),
|
||||||
|
expiration,
|
||||||
|
key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat),
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var createAPIKeyCmd = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Creates a new Api key",
|
||||||
|
Long: `
|
||||||
|
Creates a new Api key, the Api key is only visible on creation
|
||||||
|
and cannot be retrieved again.
|
||||||
|
If you loose a key, create a new one and revoke (expire) the old one.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Msg("Preparing to create ApiKey")
|
||||||
|
|
||||||
|
request := &v1.CreateApiKeyRequest{}
|
||||||
|
|
||||||
|
duration, _ := cmd.Flags().GetDuration("expiration")
|
||||||
|
expiration := time.Now().UTC().Add(duration)
|
||||||
|
|
||||||
|
log.Trace().Dur("expiration", duration).Msg("expiration has been set")
|
||||||
|
|
||||||
|
request.Expiration = timestamppb.New(expiration)
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
response, err := client.CreateApiKey(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot create Api Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.ApiKey, response.ApiKey, output)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var expireAPIKeyCmd = &cobra.Command{
|
||||||
|
Use: "expire",
|
||||||
|
Short: "Expire an ApiKey",
|
||||||
|
Aliases: []string{"revoke"},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
prefix, err := cmd.Flags().GetString("prefix")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting prefix from CLI flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ExpireApiKeyRequest{
|
||||||
|
Prefix: prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.ExpireApiKey(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot expire Api Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response, "Key expired", output)
|
||||||
|
},
|
||||||
|
}
|
||||||
41
cmd/headscale/cli/generate.go
Normal file
41
cmd/headscale/cli/generate.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(generateCmd)
|
||||||
|
generateCmd.AddCommand(generatePrivateKeyCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateCmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatePrivateKeyCmd = &cobra.Command{
|
||||||
|
Use: "private-key",
|
||||||
|
Short: "Generate a private key for the headscale server",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
|
machineKeyStr, err := machineKey.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(map[string]string{
|
||||||
|
"private_key": string(machineKeyStr),
|
||||||
|
},
|
||||||
|
string(machineKeyStr), output)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
survey "github.com/AlecAivazis/survey/v2"
|
survey "github.com/AlecAivazis/survey/v2"
|
||||||
@@ -459,7 +460,7 @@ func nodesToPtables(
|
|||||||
"Name",
|
"Name",
|
||||||
"NodeKey",
|
"NodeKey",
|
||||||
"Namespace",
|
"Namespace",
|
||||||
"IP address",
|
"IP addresses",
|
||||||
"Ephemeral",
|
"Ephemeral",
|
||||||
"Last seen",
|
"Last seen",
|
||||||
"Online",
|
"Online",
|
||||||
@@ -523,7 +524,7 @@ func nodesToPtables(
|
|||||||
machine.Name,
|
machine.Name,
|
||||||
nodeKey.ShortString(),
|
nodeKey.ShortString(),
|
||||||
namespace,
|
namespace,
|
||||||
machine.IpAddress,
|
strings.Join(machine.IpAddresses, ", "),
|
||||||
strconv.FormatBool(ephemeral),
|
strconv.FormatBool(ephemeral),
|
||||||
lastSeenTime,
|
lastSeenTime,
|
||||||
online,
|
online,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ var listPreAuthKeys = &cobra.Command{
|
|||||||
for _, key := range response.PreAuthKeys {
|
for _, key := range response.PreAuthKeys {
|
||||||
expiration := "-"
|
expiration := "-"
|
||||||
if key.GetExpiration() != nil {
|
if key.GetExpiration() != nil {
|
||||||
expiration = key.Expiration.AsTime().Format("2006-01-02 15:04:05")
|
expiration = ColourTime(key.Expiration.AsTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
var reusable string
|
var reusable string
|
||||||
|
|||||||
19
cmd/headscale/cli/pterm_style.go
Normal file
19
cmd/headscale/cli/pterm_style.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pterm/pterm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ColourTime(date time.Time) string {
|
||||||
|
dateStr := date.Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
if date.After(time.Now()) {
|
||||||
|
dateStr = pterm.LightGreen(dateStr)
|
||||||
|
} else {
|
||||||
|
dateStr = pterm.LightRed(dateStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
@@ -2,13 +2,16 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -17,12 +20,19 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PermissionFallback = 0o700
|
||||||
|
HeadscaleDateTimeFormat = "2006-01-02 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
func LoadConfig(path string) error {
|
func LoadConfig(path string) error {
|
||||||
viper.SetConfigName("config")
|
viper.SetConfigName("config")
|
||||||
if path == "" {
|
if path == "" {
|
||||||
@@ -41,16 +51,18 @@ func LoadConfig(path string) error {
|
|||||||
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
|
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
|
||||||
viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01")
|
viper.SetDefault("tls_letsencrypt_challenge_type", "HTTP-01")
|
||||||
|
|
||||||
viper.SetDefault("ip_prefix", "100.64.0.0/10")
|
|
||||||
|
|
||||||
viper.SetDefault("log_level", "info")
|
viper.SetDefault("log_level", "info")
|
||||||
|
|
||||||
viper.SetDefault("dns_config", nil)
|
viper.SetDefault("dns_config", nil)
|
||||||
|
|
||||||
viper.SetDefault("unix_socket", "/var/run/headscale.sock")
|
viper.SetDefault("unix_socket", "/var/run/headscale.sock")
|
||||||
|
viper.SetDefault("unix_socket_permission", "0o770")
|
||||||
|
|
||||||
|
viper.SetDefault("grpc_listen_addr", ":50443")
|
||||||
|
viper.SetDefault("grpc_allow_insecure", false)
|
||||||
|
|
||||||
viper.SetDefault("cli.insecure", false)
|
|
||||||
viper.SetDefault("cli.timeout", "5s")
|
viper.SetDefault("cli.timeout", "5s")
|
||||||
|
viper.SetDefault("cli.insecure", false)
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
return fmt.Errorf("fatal error reading config file: %w", err)
|
return fmt.Errorf("fatal error reading config file: %w", err)
|
||||||
@@ -221,10 +233,61 @@ func getHeadscaleConfig() headscale.Config {
|
|||||||
dnsConfig, baseDomain := GetDNSConfig()
|
dnsConfig, baseDomain := GetDNSConfig()
|
||||||
derpConfig := GetDERPConfig()
|
derpConfig := GetDERPConfig()
|
||||||
|
|
||||||
|
configuredPrefixes := viper.GetStringSlice("ip_prefixes")
|
||||||
|
parsedPrefixes := make([]netaddr.IPPrefix, 0, len(configuredPrefixes)+1)
|
||||||
|
|
||||||
|
legacyPrefixField := viper.GetString("ip_prefix")
|
||||||
|
if len(legacyPrefixField) > 0 {
|
||||||
|
log.
|
||||||
|
Warn().
|
||||||
|
Msgf(
|
||||||
|
"%s, %s",
|
||||||
|
"use of 'ip_prefix' for configuration is deprecated",
|
||||||
|
"please see 'ip_prefixes' in the shipped example.",
|
||||||
|
)
|
||||||
|
legacyPrefix, err := netaddr.ParseIPPrefix(legacyPrefixField)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to parse ip_prefix: %w", err))
|
||||||
|
}
|
||||||
|
parsedPrefixes = append(parsedPrefixes, legacyPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, prefixInConfig := range configuredPrefixes {
|
||||||
|
prefix, err := netaddr.ParseIPPrefix(prefixInConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to parse ip_prefixes[%d]: %w", i, err))
|
||||||
|
}
|
||||||
|
parsedPrefixes = append(parsedPrefixes, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes := make([]netaddr.IPPrefix, 0, len(parsedPrefixes))
|
||||||
|
{
|
||||||
|
// dedup
|
||||||
|
normalizedPrefixes := make(map[string]int, len(parsedPrefixes))
|
||||||
|
for i, p := range parsedPrefixes {
|
||||||
|
normalized, _ := p.Range().Prefix()
|
||||||
|
normalizedPrefixes[normalized.String()] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert back to list
|
||||||
|
for _, i := range normalizedPrefixes {
|
||||||
|
prefixes = append(prefixes, parsedPrefixes[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prefixes) < 1 {
|
||||||
|
prefixes = append(prefixes, netaddr.MustParseIPPrefix("100.64.0.0/10"))
|
||||||
|
log.Warn().
|
||||||
|
Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes)
|
||||||
|
}
|
||||||
|
|
||||||
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"),
|
||||||
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
|
GRPCAddr: viper.GetString("grpc_listen_addr"),
|
||||||
|
GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"),
|
||||||
|
|
||||||
|
IPPrefixes: prefixes,
|
||||||
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
||||||
BaseDomain: baseDomain,
|
BaseDomain: baseDomain,
|
||||||
|
|
||||||
@@ -257,7 +320,8 @@ func getHeadscaleConfig() headscale.Config {
|
|||||||
ACMEEmail: viper.GetString("acme_email"),
|
ACMEEmail: viper.GetString("acme_email"),
|
||||||
ACMEURL: viper.GetString("acme_url"),
|
ACMEURL: viper.GetString("acme_url"),
|
||||||
|
|
||||||
UnixSocket: viper.GetString("unix_socket"),
|
UnixSocket: viper.GetString("unix_socket"),
|
||||||
|
UnixSocketPermission: GetFileMode("unix_socket_permission"),
|
||||||
|
|
||||||
OIDC: headscale.OIDCConfig{
|
OIDC: headscale.OIDCConfig{
|
||||||
Issuer: viper.GetString("oidc.issuer"),
|
Issuer: viper.GetString("oidc.issuer"),
|
||||||
@@ -268,8 +332,8 @@ func getHeadscaleConfig() headscale.Config {
|
|||||||
CLI: headscale.CLIConfig{
|
CLI: headscale.CLIConfig{
|
||||||
Address: viper.GetString("cli.address"),
|
Address: viper.GetString("cli.address"),
|
||||||
APIKey: viper.GetString("cli.api_key"),
|
APIKey: viper.GetString("cli.api_key"),
|
||||||
Insecure: viper.GetBool("cli.insecure"),
|
|
||||||
Timeout: viper.GetDuration("cli.timeout"),
|
Timeout: viper.GetDuration("cli.timeout"),
|
||||||
|
Insecure: viper.GetBool("cli.insecure"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,14 +404,14 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
|
|||||||
|
|
||||||
grpcOptions = append(
|
grpcOptions = append(
|
||||||
grpcOptions,
|
grpcOptions,
|
||||||
grpc.WithInsecure(),
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
grpc.WithContextDialer(headscale.GrpcSocketDialer),
|
grpc.WithContextDialer(headscale.GrpcSocketDialer),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// If we are not connecting to a local server, require an API key for authentication
|
// If we are not connecting to a local server, require an API key for authentication
|
||||||
apiKey := cfg.CLI.APIKey
|
apiKey := cfg.CLI.APIKey
|
||||||
if apiKey == "" {
|
if apiKey == "" {
|
||||||
log.Fatal().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.")
|
log.Fatal().Caller().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.")
|
||||||
}
|
}
|
||||||
grpcOptions = append(grpcOptions,
|
grpcOptions = append(grpcOptions,
|
||||||
grpc.WithPerRPCCredentials(tokenAuth{
|
grpc.WithPerRPCCredentials(tokenAuth{
|
||||||
@@ -356,14 +420,27 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
|
|||||||
)
|
)
|
||||||
|
|
||||||
if cfg.CLI.Insecure {
|
if cfg.CLI.Insecure {
|
||||||
grpcOptions = append(grpcOptions, grpc.WithInsecure())
|
tlsConfig := &tls.Config{
|
||||||
|
// turn of gosec as we are intentionally setting
|
||||||
|
// insecure.
|
||||||
|
//nolint:gosec
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcOptions = append(grpcOptions,
|
||||||
|
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
grpcOptions = append(grpcOptions,
|
||||||
|
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC")
|
log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC")
|
||||||
conn, err := grpc.DialContext(ctx, address, grpcOptions...)
|
conn, err := grpc.DialContext(ctx, address, grpcOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msgf("Could not connect: %v", err)
|
log.Fatal().Caller().Err(err).Msgf("Could not connect: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := v1.NewHeadscaleServiceClient(conn)
|
client := v1.NewHeadscaleServiceClient(conn)
|
||||||
@@ -372,21 +449,21 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SuccessOutput(result interface{}, override string, outputFormat string) {
|
func SuccessOutput(result interface{}, override string, outputFormat string) {
|
||||||
var j []byte
|
var jsonBytes []byte
|
||||||
var err error
|
var err error
|
||||||
switch outputFormat {
|
switch outputFormat {
|
||||||
case "json":
|
case "json":
|
||||||
j, err = json.MarshalIndent(result, "", "\t")
|
jsonBytes, err = json.MarshalIndent(result, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
case "json-line":
|
case "json-line":
|
||||||
j, err = json.Marshal(result)
|
jsonBytes, err = json.Marshal(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
case "yaml":
|
case "yaml":
|
||||||
j, err = yaml.Marshal(result)
|
jsonBytes, err = yaml.Marshal(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
@@ -398,7 +475,7 @@ func SuccessOutput(result interface{}, override string, outputFormat string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint
|
//nolint
|
||||||
fmt.Println(string(j))
|
fmt.Println(string(jsonBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrorOutput(errResult error, override string, outputFormat string) {
|
func ErrorOutput(errResult error, override string, outputFormat string) {
|
||||||
@@ -448,3 +525,14 @@ func loadOIDCMatchMap() map[string]string {
|
|||||||
|
|
||||||
return strMap
|
return strMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFileMode(key string) fs.FileMode {
|
||||||
|
modeStr := viper.GetString(key)
|
||||||
|
|
||||||
|
mode, err := strconv.ParseUint(modeStr, headscale.Base8, headscale.BitSize64)
|
||||||
|
if err != nil {
|
||||||
|
return PermissionFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.FileMode(mode)
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err := cli.LoadConfig(""); err != nil {
|
if err := cli.LoadConfig(""); err != nil {
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Caller().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
machineOutput := cli.HasMachineOutputFlag()
|
machineOutput := cli.HasMachineOutputFlag()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -60,6 +61,11 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
|||||||
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
|
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
||||||
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
||||||
|
c.Assert(
|
||||||
|
cli.GetFileMode("unix_socket_permission"),
|
||||||
|
check.Equals,
|
||||||
|
fs.FileMode(0o770),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Suite) TestDNSConfigLoading(c *check.C) {
|
func (*Suite) TestDNSConfigLoading(c *check.C) {
|
||||||
|
|||||||
@@ -16,12 +16,32 @@ 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 for gRPC.
|
||||||
|
# gRPC is used for controlling a headscale server
|
||||||
|
# remotely with the CLI
|
||||||
|
# Note: Remote access _only_ works if you have
|
||||||
|
# valid certificates.
|
||||||
|
grpc_listen_addr: 0.0.0.0:50443
|
||||||
|
|
||||||
|
# Allow the gRPC admin interface to run in INSECURE
|
||||||
|
# mode. This is not recommended as the traffic will
|
||||||
|
# be unencrypted. Only enable if you know what you
|
||||||
|
# are doing.
|
||||||
|
grpc_allow_insecure: false
|
||||||
|
|
||||||
# Private key used encrypt the traffic between headscale
|
# Private key used encrypt the traffic between headscale
|
||||||
# and Tailscale clients.
|
# and Tailscale clients.
|
||||||
# The private key file which will be
|
# The private key file which will be
|
||||||
# autogenerated if it's missing
|
# autogenerated if it's missing
|
||||||
private_key_path: /var/lib/headscale/private.key
|
private_key_path: /var/lib/headscale/private.key
|
||||||
|
|
||||||
|
# List of IP prefixes to allocate tailaddresses from.
|
||||||
|
# Each prefix consists of either an IPv4 or IPv6 address,
|
||||||
|
# and the associated prefix length, delimited by a slash.
|
||||||
|
ip_prefixes:
|
||||||
|
- fd7a:115c:a1e0::/48
|
||||||
|
- 100.64.0.0/10
|
||||||
|
|
||||||
# DERP is a relay system that Tailscale uses when a direct
|
# DERP is a relay system that Tailscale uses when a direct
|
||||||
# connection cannot be established.
|
# connection cannot be established.
|
||||||
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
|
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
|
||||||
@@ -149,6 +169,7 @@ dns_config:
|
|||||||
# Note: for local development, you probably want to change this to:
|
# Note: for local development, you probably want to change this to:
|
||||||
# unix_socket: ./headscale.sock
|
# unix_socket: ./headscale.sock
|
||||||
unix_socket: /var/run/headscale.sock
|
unix_socket: /var/run/headscale.sock
|
||||||
|
unix_socket_permission: "0770"
|
||||||
#
|
#
|
||||||
# headscale supports experimental OpenID connect support,
|
# headscale supports experimental OpenID connect support,
|
||||||
# it is still being tested and might have some bugs, please
|
# it is still being tested and might have some bugs, please
|
||||||
|
|||||||
13
db.go
13
db.go
@@ -28,20 +28,26 @@ func (h *Headscale) initDB() error {
|
|||||||
h.db = db
|
h.db = db
|
||||||
|
|
||||||
if h.dbType == Postgres {
|
if h.dbType == Postgres {
|
||||||
db.Exec("create extension if not exists \"uuid-ossp\";")
|
db.Exec(`create extension if not exists "uuid-ossp";`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = db.Migrator().RenameColumn(&Machine{}, "ip_address", "ip_addresses")
|
||||||
|
|
||||||
err = db.AutoMigrate(&Machine{})
|
err = db.AutoMigrate(&Machine{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(&KV{})
|
err = db.AutoMigrate(&KV{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(&Namespace{})
|
err = db.AutoMigrate(&Namespace{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(&PreAuthKey{})
|
err = db.AutoMigrate(&PreAuthKey{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -52,6 +58,11 @@ func (h *Headscale) initDB() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = db.AutoMigrate(&APIKey{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = h.setValue("db_version", dbVersion)
|
err = h.setValue("db_version", dbVersion)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
109
dns.go
109
dns.go
@@ -14,6 +14,11 @@ const (
|
|||||||
ByteSize = 8
|
ByteSize = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv4AddressLength = 32
|
||||||
|
ipv6AddressLength = 128
|
||||||
|
)
|
||||||
|
|
||||||
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
|
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
|
||||||
// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
|
// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
|
||||||
// server (listening in 100.100.100.100 udp/53) should be used for.
|
// server (listening in 100.100.100.100 udp/53) should be used for.
|
||||||
@@ -34,14 +39,33 @@ const (
|
|||||||
|
|
||||||
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
||||||
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
||||||
func generateMagicDNSRootDomains(
|
func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
|
||||||
ipPrefix netaddr.IPPrefix,
|
fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes))
|
||||||
) []dnsname.FQDN {
|
for _, ipPrefix := range ipPrefixes {
|
||||||
// TODO(juanfont): we are not handing out IPv6 addresses yet
|
var generateDNSRoot func(netaddr.IPPrefix) []dnsname.FQDN
|
||||||
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
|
switch ipPrefix.IP().BitLen() {
|
||||||
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
|
case ipv4AddressLength:
|
||||||
fqdns := []dnsname.FQDN{ipv6base}
|
generateDNSRoot = generateIPv4DNSRootDomain
|
||||||
|
|
||||||
|
case ipv6AddressLength:
|
||||||
|
generateDNSRoot = generateIPv6DNSRootDomain
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"unsupported IP version with address length %d",
|
||||||
|
ipPrefix.IP().BitLen(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fqdns = append(fqdns, generateDNSRoot(ipPrefix)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fqdns
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateIPv4DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
|
||||||
// Conversion to the std lib net.IPnet, a bit easier to operate
|
// Conversion to the std lib net.IPnet, a bit easier to operate
|
||||||
netRange := ipPrefix.IPNet()
|
netRange := ipPrefix.IPNet()
|
||||||
maskBits, _ := netRange.Mask.Size()
|
maskBits, _ := netRange.Mask.Size()
|
||||||
@@ -65,6 +89,7 @@ func generateMagicDNSRootDomains(
|
|||||||
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
|
rdnsSlice = append(rdnsSlice, "in-addr.arpa.")
|
||||||
rdnsBase := strings.Join(rdnsSlice, ".")
|
rdnsBase := strings.Join(rdnsSlice, ".")
|
||||||
|
|
||||||
|
fqdns := make([]dnsname.FQDN, 0, max-min+1)
|
||||||
for i := min; i <= max; i++ {
|
for i := min; i <= max; i++ {
|
||||||
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase))
|
fqdn, err := dnsname.ToFQDN(fmt.Sprintf("%d.%s", i, rdnsBase))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,6 +101,56 @@ func generateMagicDNSRootDomains(
|
|||||||
return fqdns
|
return fqdns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateIPv6DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
|
||||||
|
const nibbleLen = 4
|
||||||
|
|
||||||
|
maskBits, _ := ipPrefix.IPNet().Mask.Size()
|
||||||
|
expanded := ipPrefix.IP().StringExpanded()
|
||||||
|
nibbleStr := strings.Map(func(r rune) rune {
|
||||||
|
if r == ':' {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}, expanded)
|
||||||
|
|
||||||
|
// TODO?: that does not look the most efficient implementation,
|
||||||
|
// but the inputs are not so long as to cause problems,
|
||||||
|
// and from what I can see, the generateMagicDNSRootDomains
|
||||||
|
// function is called only once over the lifetime of a server process.
|
||||||
|
prefixConstantParts := []string{}
|
||||||
|
for i := 0; i < maskBits/nibbleLen; i++ {
|
||||||
|
prefixConstantParts = append(
|
||||||
|
[]string{string(nibbleStr[i])},
|
||||||
|
prefixConstantParts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
makeDomain := func(variablePrefix ...string) (dnsname.FQDN, error) {
|
||||||
|
prefix := strings.Join(append(variablePrefix, prefixConstantParts...), ".")
|
||||||
|
|
||||||
|
return dnsname.ToFQDN(fmt.Sprintf("%s.ip6.arpa", prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
var fqdns []dnsname.FQDN
|
||||||
|
if maskBits%4 == 0 {
|
||||||
|
dom, _ := makeDomain()
|
||||||
|
fqdns = append(fqdns, dom)
|
||||||
|
} else {
|
||||||
|
domCount := 1 << (maskBits % nibbleLen)
|
||||||
|
fqdns = make([]dnsname.FQDN, 0, domCount)
|
||||||
|
for i := 0; i < domCount; i++ {
|
||||||
|
varNibble := fmt.Sprintf("%x", i)
|
||||||
|
dom, err := makeDomain(varNibble)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fqdns = append(fqdns, dom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fqdns
|
||||||
|
}
|
||||||
|
|
||||||
func getMapResponseDNSConfig(
|
func getMapResponseDNSConfig(
|
||||||
dnsConfigOrig *tailcfg.DNSConfig,
|
dnsConfigOrig *tailcfg.DNSConfig,
|
||||||
baseDomain string,
|
baseDomain string,
|
||||||
@@ -88,7 +163,15 @@ func getMapResponseDNSConfig(
|
|||||||
dnsConfig = dnsConfigOrig.Clone()
|
dnsConfig = dnsConfigOrig.Clone()
|
||||||
dnsConfig.Domains = append(
|
dnsConfig.Domains = append(
|
||||||
dnsConfig.Domains,
|
dnsConfig.Domains,
|
||||||
fmt.Sprintf("%s.%s", machine.Namespace.Name, baseDomain),
|
fmt.Sprintf(
|
||||||
|
"%s.%s",
|
||||||
|
strings.ReplaceAll(
|
||||||
|
machine.Namespace.Name,
|
||||||
|
"@",
|
||||||
|
".",
|
||||||
|
), // Replace @ with . for valid domain for machine
|
||||||
|
baseDomain,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
namespaceSet := set.New(set.ThreadSafe)
|
namespaceSet := set.New(set.ThreadSafe)
|
||||||
@@ -96,8 +179,14 @@ func getMapResponseDNSConfig(
|
|||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
namespaceSet.Add(p.Namespace)
|
namespaceSet.Add(p.Namespace)
|
||||||
}
|
}
|
||||||
for _, namespace := range namespaceSet.List() {
|
for _, ns := range namespaceSet.List() {
|
||||||
dnsRoute := fmt.Sprintf("%s.%s", namespace.(Namespace).Name, baseDomain)
|
namespace, ok := ns.(Namespace)
|
||||||
|
if !ok {
|
||||||
|
dnsConfig = dnsConfigOrig
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dnsRoute := fmt.Sprintf("%v.%v", namespace.Name, baseDomain)
|
||||||
dnsConfig.Routes[dnsRoute] = nil
|
dnsConfig.Routes[dnsRoute] = nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
66
dns_test.go
66
dns_test.go
@@ -10,8 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
||||||
prefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
|
prefixes := []netaddr.IPPrefix{
|
||||||
domains := generateMagicDNSRootDomains(prefix)
|
netaddr.MustParseIPPrefix("100.64.0.0/10"),
|
||||||
|
}
|
||||||
|
domains := generateMagicDNSRootDomains(prefixes)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
@@ -45,8 +47,10 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
||||||
prefix := netaddr.MustParseIPPrefix("172.16.0.0/16")
|
prefixes := []netaddr.IPPrefix{
|
||||||
domains := generateMagicDNSRootDomains(prefix)
|
netaddr.MustParseIPPrefix("172.16.0.0/16"),
|
||||||
|
}
|
||||||
|
domains := generateMagicDNSRootDomains(prefixes)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
@@ -69,6 +73,44 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
|||||||
c.Assert(found, check.Equals, true)
|
c.Assert(found, check.Equals, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Happens when netmask is a multiple of 4 bits (sounds likely).
|
||||||
|
func (s *Suite) TestMagicDNSRootDomainsIPv6Single(c *check.C) {
|
||||||
|
prefixes := []netaddr.IPPrefix{
|
||||||
|
netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48"),
|
||||||
|
}
|
||||||
|
domains := generateMagicDNSRootDomains(prefixes)
|
||||||
|
|
||||||
|
c.Assert(len(domains), check.Equals, 1)
|
||||||
|
c.Assert(
|
||||||
|
domains[0].WithTrailingDot(),
|
||||||
|
check.Equals,
|
||||||
|
"0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) {
|
||||||
|
prefixes := []netaddr.IPPrefix{
|
||||||
|
netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/50"),
|
||||||
|
}
|
||||||
|
domains := generateMagicDNSRootDomains(prefixes)
|
||||||
|
|
||||||
|
yieldsRoot := func(dom string) bool {
|
||||||
|
for _, candidate := range domains {
|
||||||
|
if candidate.WithTrailingDot() == dom {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(len(domains), check.Equals, 4)
|
||||||
|
c.Assert(yieldsRoot("0.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
|
||||||
|
c.Assert(yieldsRoot("1.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
|
||||||
|
c.Assert(yieldsRoot("2.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
|
||||||
|
c.Assert(yieldsRoot("3.0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa."), check.Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
namespaceShared1, err := app.CreateNamespace("shared1")
|
namespaceShared1, err := app.CreateNamespace("shared1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -124,7 +166,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.1",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared1)
|
app.db.Save(machineInShared1)
|
||||||
@@ -142,7 +184,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.2",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared2)
|
app.db.Save(machineInShared2)
|
||||||
@@ -160,7 +202,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.3",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared3)
|
app.db.Save(machineInShared3)
|
||||||
@@ -178,7 +220,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine2InShared1)
|
app.db.Save(machine2InShared1)
|
||||||
@@ -273,7 +315,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.1",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared1)
|
app.db.Save(machineInShared1)
|
||||||
@@ -291,7 +333,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.2",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared2)
|
app.db.Save(machineInShared2)
|
||||||
@@ -309,7 +351,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.3",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared3)
|
app.db.Save(machineInShared3)
|
||||||
@@ -327,7 +369,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine2InShared1)
|
app.db.Save(machine2InShared1)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ please ask on [Discord](https://discord.gg/XcQxk2VHjx) instead of opening an Iss
|
|||||||
### How-to
|
### How-to
|
||||||
|
|
||||||
- [Running headscale on Linux](running-headscale-linux.md)
|
- [Running headscale on Linux](running-headscale-linux.md)
|
||||||
|
- [Control headscale remotely](remote-cli.md)
|
||||||
|
- [Using a Windows client with headscale](windows-client.md)
|
||||||
|
|
||||||
### References
|
### References
|
||||||
|
|
||||||
@@ -37,6 +39,14 @@ use namespaces (which are the equivalent to user/logins in Tailscale.com).
|
|||||||
|
|
||||||
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
|
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
|
||||||
|
|
||||||
|
When using ACL's the Namespace borders are no longer applied. All machines
|
||||||
|
whichever the Namespace have the ability to communicate with other hosts as
|
||||||
|
long as the ACL's permits this exchange.
|
||||||
|
|
||||||
|
The [ACLs](acls.md) document should help understand a fictional case of setting
|
||||||
|
up ACLs in a small company. All concepts presented in this document could be
|
||||||
|
applied outside of business oriented usage.
|
||||||
|
|
||||||
### Apple devices
|
### Apple devices
|
||||||
|
|
||||||
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
|
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
|
||||||
|
|||||||
141
docs/acls.md
Normal file
141
docs/acls.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# ACLs use case 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 developers 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
|
||||||
|
|
||||||
|
## Setup of the network
|
||||||
|
|
||||||
|
Let's create the namespaces. Each user should have his own namespace. The users
|
||||||
|
here are represented as 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 collections of users having a common scope. A user can be in multiple groups
|
||||||
|
// groups cannot be composed of groups
|
||||||
|
"groups": {
|
||||||
|
"group:boss": ["boss"],
|
||||||
|
"group:dev": ["dev1", "dev2"],
|
||||||
|
"group:admin": ["admin1"],
|
||||||
|
"group:intern": ["intern1"]
|
||||||
|
},
|
||||||
|
// tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.
|
||||||
|
// This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)
|
||||||
|
// and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// developers have access to databases servers and application servers on all ports
|
||||||
|
// they can only view the applications servers in prod and have no access to databases servers in production
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
// applications servers
|
||||||
|
{
|
||||||
|
"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.
|
||||||
|
{ "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:*"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
docs/images/windows-registry.png
Normal file
BIN
docs/images/windows-registry.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
100
docs/remote-cli.md
Normal file
100
docs/remote-cli.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Controlling `headscale` with remote CLI
|
||||||
|
|
||||||
|
## Prerequisit
|
||||||
|
|
||||||
|
- A workstation to run `headscale` (could be Linux, macOS, other supported platforms)
|
||||||
|
- A `headscale` server (version `0.13.0` or newer)
|
||||||
|
- Access to create API keys (local access to the `headscale` server)
|
||||||
|
- `headscale` _must_ be served over TLS/HTTPS
|
||||||
|
- Remote access does _not_ support unencrypted traffic.
|
||||||
|
- Port `50443` must be open in the firewall (or port overriden by `grpc_listen_addr` option)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
This documentation has the goal of showing a user how-to set control a `headscale` instance
|
||||||
|
from a remote machine with the `headscale` command line binary.
|
||||||
|
|
||||||
|
## Create an API key
|
||||||
|
|
||||||
|
We need to create an API key to authenticate our remote `headscale` when using it from our workstation.
|
||||||
|
|
||||||
|
To create a API key, log into your `headscale` server and generate a key:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale apikeys create --expiration 90d
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the output of the command and save it for later. Please not that you can not retrieve a key again,
|
||||||
|
if the key is lost, expire the old one, and create a new key.
|
||||||
|
|
||||||
|
To list the keys currently assosicated with the server:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale apikeys list
|
||||||
|
```
|
||||||
|
|
||||||
|
and to expire a key:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale apikeys expire --prefix "<PREFIX>"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Download and configure `headscale`
|
||||||
|
|
||||||
|
1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases):
|
||||||
|
|
||||||
|
2. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headcale`
|
||||||
|
|
||||||
|
3. Make `headscale` executable:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
chmod +x /usr/local/bin/headscale
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Configure the CLI through Environment Variables
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export HEADSCALE_CLI_ADDRESS="<HEADSCALE ADDRESS>:<PORT>"
|
||||||
|
export HEADSCALE_CLI_API_KEY="<API KEY FROM PREVIOUS STAGE>"
|
||||||
|
```
|
||||||
|
|
||||||
|
for example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export HEADSCALE_CLI_ADDRESS="headscale.example.com:50443"
|
||||||
|
export HEADSCALE_CLI_API_KEY="abcde12345"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will tell the `headscale` binary to connect to a remote instance, instead of looking
|
||||||
|
for a local instance (which is what it does on the server).
|
||||||
|
|
||||||
|
The API key is needed to make sure that your are allowed to access the server. The key is _not_
|
||||||
|
needed when running directly on the server, as the connection is local.
|
||||||
|
|
||||||
|
5. Test the connection
|
||||||
|
|
||||||
|
Let us run the headscale command to verify that we can connect by listing our nodes:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale nodes list
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now be able to see a list of your nodes from your workstation, and you can
|
||||||
|
now control the `headscale` server from your workstation.
|
||||||
|
|
||||||
|
## Behind a proxy
|
||||||
|
|
||||||
|
It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as `headscale`.
|
||||||
|
|
||||||
|
While this is _not a supported_ feature, an example on how this can be set up on
|
||||||
|
[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Checklist:
|
||||||
|
|
||||||
|
- Make sure you have the _same_ `headscale` version on your server and workstation
|
||||||
|
- Make sure you use version `0.13.0` or newer.
|
||||||
|
- Verify that your TLS certificate is valid and trusted
|
||||||
|
- If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or
|
||||||
|
- Set `HEADSCALE_CLI_INSECURE` to 0 in your environement
|
||||||
@@ -21,7 +21,7 @@ wget --output-document=/usr/local/bin/headscale \
|
|||||||
chmod +x /usr/local/bin/headscale
|
chmod +x /usr/local/bin/headscale
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Prepare a direction to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database:
|
3. Prepare a directory to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Directory for configuration
|
# Directory for configuration
|
||||||
@@ -32,7 +32,7 @@ mkdir -p /etc/headscale
|
|||||||
mkdir -p /var/lib/headscale
|
mkdir -p /var/lib/headscale
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Create an empty SQlite datebase:
|
4. Create an empty SQLite database:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
touch /var/lib/headscale/db.sqlite
|
touch /var/lib/headscale/db.sqlite
|
||||||
@@ -44,7 +44,7 @@ touch /var/lib/headscale/db.sqlite
|
|||||||
touch /etc/headscale/config.yaml
|
touch /etc/headscale/config.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
It is **strongly recommended** to copy and modifiy the [example configuration](../config-example.yaml)
|
It is **strongly recommended** to copy and modify the [example configuration](../config-example.yaml)
|
||||||
from the [headscale repository](../)
|
from the [headscale repository](../)
|
||||||
|
|
||||||
6. Start the headscale server:
|
6. Start the headscale server:
|
||||||
@@ -106,7 +106,7 @@ tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
|
|||||||
|
|
||||||
## Running `headscale` in the background with SystemD
|
## Running `headscale` in the background with SystemD
|
||||||
|
|
||||||
In this section it will be demonstrated how to run `headscale` as a service in the background with [SystemD](https://www.freedesktop.org/wiki/Software/systemd/).
|
This section demonstrates how to run `headscale` as a service in the background with [SystemD](https://www.freedesktop.org/wiki/Software/systemd/).
|
||||||
This should work on most modern Linux distributions.
|
This should work on most modern Linux distributions.
|
||||||
|
|
||||||
1. Create a SystemD service configuration at `/etc/systemd/system/headscale.service` containing:
|
1. Create a SystemD service configuration at `/etc/systemd/system/headscale.service` containing:
|
||||||
@@ -138,6 +138,18 @@ RuntimeDirectory=headscale
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that when running as the headscale user ensure that, either you add your current user to the headscale group:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
usermod -a -G headscale current_user
|
||||||
|
```
|
||||||
|
|
||||||
|
or run all headscale commands as the headscale user:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
su - headscale
|
||||||
|
```
|
||||||
|
|
||||||
2. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a SystemD friendly path:
|
2. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a SystemD friendly path:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
50
docs/windows-client.md
Normal file
50
docs/windows-client.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Connecting a Windows client
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
This documentation has the goal of showing how a user can use the official Windows [Tailscale](https://tailscale.com) client with `headscale`.
|
||||||
|
|
||||||
|
## Add registry keys
|
||||||
|
|
||||||
|
To make the Windows client behave as expected and to run well with `headscale`, two registry keys **must** be set:
|
||||||
|
|
||||||
|
- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` as a `string` type, to allow Tailscale to run properly in the background
|
||||||
|
- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `<YOUR HEADSCALE URL>` as a `string` type, to ensure Tailscale contacts the correct control server.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798).
|
||||||
|
|
||||||
|
For a guide on how to edit registry keys, [check out Computer Hope](https://www.computerhope.com/issues/ch001348.htm).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Download the [Official Windows Client](https://tailscale.com/download/windows) and install it.
|
||||||
|
|
||||||
|
When the installation has finished, start Tailscale and log in (you might have to click the icon in the system tray).
|
||||||
|
|
||||||
|
The log in should open a browser Window and direct you to your `headscale` instance.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you are seeing repeated messages like:
|
||||||
|
|
||||||
|
```
|
||||||
|
[GIN] 2022/02/10 - 16:39:34 | 200 | 1.105306ms | 127.0.0.1 | POST "/machine/redacted"
|
||||||
|
```
|
||||||
|
|
||||||
|
in your `headscale` output, turn on `DEBUG` logging and look for:
|
||||||
|
|
||||||
|
```
|
||||||
|
2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted
|
||||||
|
```
|
||||||
|
|
||||||
|
This typically means that the registry keys above was not set appropriately.
|
||||||
|
|
||||||
|
To reset and try again, it is important to do the following:
|
||||||
|
|
||||||
|
1. Ensure the registry keys from the previous guide is correctly set.
|
||||||
|
2. Shut down the Tailscale service (or the client running in the tray)
|
||||||
|
3. Delete Tailscale Application data folder, located at `C:\Users\<USERNAME>\AppData\Local\Tailscale` and try to connect again.
|
||||||
|
4. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
|
||||||
|
5. Start Tailscale on the windows machine and retry the login.
|
||||||
559
gen/go/headscale/v1/apikey.pb.go
Normal file
559
gen/go/headscale/v1/apikey.pb.go
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc (unknown)
|
||||||
|
// source: headscale/v1/apikey.proto
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiKey struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||||
|
Expiration *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||||
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
|
LastSeen *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) Reset() {
|
||||||
|
*x = ApiKey{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ApiKey) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ApiKey) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ApiKey.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ApiKey) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) GetId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) GetPrefix() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Prefix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) GetExpiration() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Expiration
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) GetCreatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.CreatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ApiKey) GetLastSeen() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.LastSeen
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateApiKeyRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Expiration *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyRequest) Reset() {
|
||||||
|
*x = CreateApiKeyRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateApiKeyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreateApiKeyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateApiKeyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyRequest) GetExpiration() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Expiration
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateApiKeyResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ApiKey string `protobuf:"bytes,1,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyResponse) Reset() {
|
||||||
|
*x = CreateApiKeyResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateApiKeyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreateApiKeyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateApiKeyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateApiKeyResponse) GetApiKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ApiKey
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpireApiKeyRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyRequest) Reset() {
|
||||||
|
*x = ExpireApiKeyRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExpireApiKeyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExpireApiKeyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExpireApiKeyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyRequest) GetPrefix() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Prefix
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpireApiKeyResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyResponse) Reset() {
|
||||||
|
*x = ExpireApiKeyResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExpireApiKeyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExpireApiKeyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExpireApiKeyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExpireApiKeyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListApiKeysRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListApiKeysRequest) Reset() {
|
||||||
|
*x = ListApiKeysRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListApiKeysRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListApiKeysRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListApiKeysRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListApiKeysRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListApiKeysRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListApiKeysResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
ApiKeys []*ApiKey `protobuf:"bytes,1,rep,name=api_keys,json=apiKeys,proto3" json:"api_keys,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListApiKeysResponse) Reset() {
|
||||||
|
*x = ListApiKeysResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListApiKeysResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListApiKeysResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListApiKeysResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_apikey_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListApiKeysResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListApiKeysResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListApiKeysResponse) GetApiKeys() []*ApiKey {
|
||||||
|
if x != nil {
|
||||||
|
return x.ApiKeys
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_headscale_v1_apikey_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_headscale_v1_apikey_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61,
|
||||||
|
0x70, 0x69, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
|
||||||
|
0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, 0x01, 0x0a, 0x06, 0x41,
|
||||||
|
0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18,
|
||||||
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x3a, 0x0a,
|
||||||
|
0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65,
|
||||||
|
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
|
||||||
|
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||||
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||||
|
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||||
|
0x65, 0x64, 0x41, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65,
|
||||||
|
0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
|
0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x22, 0x51, 0x0a,
|
||||||
|
0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,
|
||||||
|
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||||
|
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
|
0x22, 0x2f, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f,
|
||||||
|
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65,
|
||||||
|
0x79, 0x22, 0x2d, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,
|
||||||
|
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66,
|
||||||
|
0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
|
||||||
|
0x22, 0x16, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74,
|
||||||
|
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x46,
|
||||||
|
0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79,
|
||||||
|
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x61,
|
||||||
|
0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||||
|
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65,
|
||||||
|
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76,
|
||||||
|
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_headscale_v1_apikey_proto_rawDescOnce sync.Once
|
||||||
|
file_headscale_v1_apikey_proto_rawDescData = file_headscale_v1_apikey_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_headscale_v1_apikey_proto_rawDescGZIP() []byte {
|
||||||
|
file_headscale_v1_apikey_proto_rawDescOnce.Do(func() {
|
||||||
|
file_headscale_v1_apikey_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_apikey_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_headscale_v1_apikey_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_headscale_v1_apikey_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
|
var file_headscale_v1_apikey_proto_goTypes = []interface{}{
|
||||||
|
(*ApiKey)(nil), // 0: headscale.v1.ApiKey
|
||||||
|
(*CreateApiKeyRequest)(nil), // 1: headscale.v1.CreateApiKeyRequest
|
||||||
|
(*CreateApiKeyResponse)(nil), // 2: headscale.v1.CreateApiKeyResponse
|
||||||
|
(*ExpireApiKeyRequest)(nil), // 3: headscale.v1.ExpireApiKeyRequest
|
||||||
|
(*ExpireApiKeyResponse)(nil), // 4: headscale.v1.ExpireApiKeyResponse
|
||||||
|
(*ListApiKeysRequest)(nil), // 5: headscale.v1.ListApiKeysRequest
|
||||||
|
(*ListApiKeysResponse)(nil), // 6: headscale.v1.ListApiKeysResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_headscale_v1_apikey_proto_depIdxs = []int32{
|
||||||
|
7, // 0: headscale.v1.ApiKey.expiration:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // 1: headscale.v1.ApiKey.created_at:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // 2: headscale.v1.ApiKey.last_seen:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // 3: headscale.v1.CreateApiKeyRequest.expiration:type_name -> google.protobuf.Timestamp
|
||||||
|
0, // 4: headscale.v1.ListApiKeysResponse.api_keys:type_name -> headscale.v1.ApiKey
|
||||||
|
5, // [5:5] is the sub-list for method output_type
|
||||||
|
5, // [5:5] is the sub-list for method input_type
|
||||||
|
5, // [5:5] is the sub-list for extension type_name
|
||||||
|
5, // [5:5] is the sub-list for extension extendee
|
||||||
|
0, // [0:5] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_headscale_v1_apikey_proto_init() }
|
||||||
|
func file_headscale_v1_apikey_proto_init() {
|
||||||
|
if File_headscale_v1_apikey_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ApiKey); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreateApiKeyRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreateApiKeyResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExpireApiKeyRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExpireApiKeyResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListApiKeysRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_apikey_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListApiKeysResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_headscale_v1_apikey_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 7,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_headscale_v1_apikey_proto_goTypes,
|
||||||
|
DependencyIndexes: file_headscale_v1_apikey_proto_depIdxs,
|
||||||
|
MessageInfos: file_headscale_v1_apikey_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_headscale_v1_apikey_proto = out.File
|
||||||
|
file_headscale_v1_apikey_proto_rawDesc = nil
|
||||||
|
file_headscale_v1_apikey_proto_goTypes = nil
|
||||||
|
file_headscale_v1_apikey_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/device.proto
|
// source: headscale/v1/device.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/headscale.proto
|
// source: headscale/v1/headscale.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
_ "google.golang.org/genproto/googleapis/api/annotations"
|
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,162 +35,185 @@ var file_headscale_v1_headscale_proto_rawDesc = []byte{
|
|||||||
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||||
0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
|
0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76,
|
0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76,
|
||||||
0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xf4,
|
0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19,
|
||||||
0x12, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76,
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69,
|
||||||
0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xcb, 0x15, 0x0a, 0x10, 0x48, 0x65,
|
||||||
0x61, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x77,
|
||||||
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52,
|
0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
|
||||||
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93,
|
0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65,
|
0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73,
|
||||||
0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x7c, 0x0a, 0x0f,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f,
|
||||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,
|
0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||||
0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43,
|
0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x7c, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65,
|
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
|
||||||
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73,
|
|
||||||
0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3,
|
|
||||||
0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61,
|
|
||||||
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x96, 0x01, 0x0a, 0x0f, 0x52,
|
|
||||||
0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24,
|
|
||||||
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
|
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71,
|
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
|
||||||
0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4,
|
|
||||||
0x93, 0x02, 0x30, 0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d,
|
|
||||||
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
|
||||||
0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61,
|
|
||||||
0x6d, 0x65, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61,
|
|
||||||
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
|
||||||
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d,
|
|
||||||
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
|
|
||||||
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
|
|
||||||
0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x61,
|
|
||||||
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f,
|
|
||||||
0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x76, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61,
|
|
||||||
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
|
||||||
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65,
|
|
||||||
0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
|
|
||||||
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
|
|
||||||
0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70,
|
|
||||||
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x80,
|
|
||||||
0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
|
|
||||||
0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
|
||||||
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
|
|
||||||
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61,
|
|
||||||
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69,
|
0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x3a, 0x01,
|
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52,
|
||||||
0x2a, 0x12, 0x87, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22,
|
||||||
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41,
|
0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x96, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e,
|
||||||
0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f,
|
0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
|
||||||
0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65,
|
0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65,
|
||||||
0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x0f, 0x4c,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x22, 0x2e,
|
||||||
0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24,
|
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
|
||||||
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
|
0x65, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x6e,
|
||||||
0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71,
|
0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x80,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b,
|
0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4,
|
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
|
||||||
0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65,
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75,
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61,
|
||||||
0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x27,
|
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
|
0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
|
||||||
0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
0x7d, 0x12, 0x76, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61,
|
0x63, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||||
0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
0x76, 0x31, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65,
|
||||||
0x3a, 0x01, 0x2a, 0x12, 0x75, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19,
|
||||||
0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
|
||||||
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x10, 0x43, 0x72,
|
||||||
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25,
|
||||||
0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70,
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x61,
|
0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65,
|
||||||
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d,
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x52,
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75,
|
||||||
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x24,
|
0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82,
|
||||||
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
|
0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70,
|
||||||
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71,
|
0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x87, 0x01, 0x0a,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68,
|
0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4,
|
0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
|
||||||
0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63,
|
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x7e, 0x0a,
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72,
|
||||||
0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22,
|
0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
|
0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
|
||||||
0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x2f, 0x65, 0x78, 0x70,
|
||||||
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72,
|
||||||
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
|
0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65,
|
||||||
0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x85, 0x01,
|
0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
|
||||||
0x0a, 0x0d, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12,
|
0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65,
|
||||||
0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12,
|
||||||
0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75,
|
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b,
|
||||||
0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
0x65, 0x79, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61,
|
||||||
0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x27, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72,
|
||||||
0x22, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65,
|
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63,
|
0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3,
|
||||||
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65,
|
||||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
0x62, 0x75, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x75,
|
||||||
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68,
|
0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x1f, 0x2e, 0x68,
|
||||||
0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3,
|
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d,
|
||||||
0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61,
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
|
||||||
0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61,
|
0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82,
|
0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0xd3, 0xe4, 0x93, 0x02, 0x30, 0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f,
|
0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x69, 0x64, 0x7d, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73,
|
0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52,
|
||||||
0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x95, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72,
|
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||||
0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x18,
|
||||||
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
|
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x7e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65,
|
||||||
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73,
|
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x22, 0x30, 0x2f, 0x61, 0x70,
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
|
||||||
|
0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a, 0x1c, 0x2f, 0x61, 0x70, 0x69,
|
||||||
|
0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63,
|
||||||
|
0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x85, 0x01, 0x0a, 0x0d, 0x45, 0x78, 0x70,
|
||||||
|
0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22, 0x2e, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65,
|
||||||
|
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78,
|
||||||
|
0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x23, 0x2f, 0x61, 0x70,
|
||||||
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61,
|
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61,
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, 0x6e, 0x73, 0x68, 0x61, 0x72,
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
|
||||||
0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x8b, 0x01,
|
0x12, 0x6e, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73,
|
||||||
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74,
|
0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52,
|
||||||
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12,
|
||||||
0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b,
|
0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
|
0x12, 0x8d, 0x01, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30,
|
||||||
|
0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x73,
|
||||||
|
0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d,
|
||||||
|
0x12, 0x95, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68,
|
||||||
|
0x69, 0x6e, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
||||||
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38,
|
||||||
|
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x22, 0x30, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
|
||||||
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x97, 0x01, 0x0a, 0x13,
|
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61,
|
||||||
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75,
|
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x8b, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74,
|
||||||
0x74, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x24, 0x2e, 0x68,
|
||||||
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d,
|
||||||
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e,
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61,
|
0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25,
|
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02,
|
||||||
0x22, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x25, 0x12, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72,
|
0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f,
|
||||||
0x6f, 0x75, 0x74, 0x65, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x97, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x61, 0x62, 0x6c,
|
||||||
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61,
|
0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x28,
|
||||||
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31,
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||||
|
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x23, 0x2f, 0x61, 0x70,
|
||||||
|
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x12, 0x70, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,
|
||||||
|
0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
|
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75,
|
||||||
|
0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x22,
|
||||||
|
0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x3a,
|
||||||
|
0x01, 0x2a, 0x12, 0x77, 0x0a, 0x0c, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b,
|
||||||
|
0x65, 0x79, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
||||||
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,
|
||||||
|
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02,
|
||||||
|
0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65,
|
||||||
|
0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0b, 0x4c,
|
||||||
|
0x69, 0x73, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x70,
|
||||||
|
0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68,
|
||||||
|
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
||||||
|
0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
|
0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
|
||||||
|
0x2f, 0x61, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||||
|
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68,
|
||||||
|
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f,
|
||||||
|
0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_headscale_v1_headscale_proto_goTypes = []interface{}{
|
var file_headscale_v1_headscale_proto_goTypes = []interface{}{
|
||||||
@@ -211,24 +235,30 @@ var file_headscale_v1_headscale_proto_goTypes = []interface{}{
|
|||||||
(*UnshareMachineRequest)(nil), // 15: headscale.v1.UnshareMachineRequest
|
(*UnshareMachineRequest)(nil), // 15: headscale.v1.UnshareMachineRequest
|
||||||
(*GetMachineRouteRequest)(nil), // 16: headscale.v1.GetMachineRouteRequest
|
(*GetMachineRouteRequest)(nil), // 16: headscale.v1.GetMachineRouteRequest
|
||||||
(*EnableMachineRoutesRequest)(nil), // 17: headscale.v1.EnableMachineRoutesRequest
|
(*EnableMachineRoutesRequest)(nil), // 17: headscale.v1.EnableMachineRoutesRequest
|
||||||
(*GetNamespaceResponse)(nil), // 18: headscale.v1.GetNamespaceResponse
|
(*CreateApiKeyRequest)(nil), // 18: headscale.v1.CreateApiKeyRequest
|
||||||
(*CreateNamespaceResponse)(nil), // 19: headscale.v1.CreateNamespaceResponse
|
(*ExpireApiKeyRequest)(nil), // 19: headscale.v1.ExpireApiKeyRequest
|
||||||
(*RenameNamespaceResponse)(nil), // 20: headscale.v1.RenameNamespaceResponse
|
(*ListApiKeysRequest)(nil), // 20: headscale.v1.ListApiKeysRequest
|
||||||
(*DeleteNamespaceResponse)(nil), // 21: headscale.v1.DeleteNamespaceResponse
|
(*GetNamespaceResponse)(nil), // 21: headscale.v1.GetNamespaceResponse
|
||||||
(*ListNamespacesResponse)(nil), // 22: headscale.v1.ListNamespacesResponse
|
(*CreateNamespaceResponse)(nil), // 22: headscale.v1.CreateNamespaceResponse
|
||||||
(*CreatePreAuthKeyResponse)(nil), // 23: headscale.v1.CreatePreAuthKeyResponse
|
(*RenameNamespaceResponse)(nil), // 23: headscale.v1.RenameNamespaceResponse
|
||||||
(*ExpirePreAuthKeyResponse)(nil), // 24: headscale.v1.ExpirePreAuthKeyResponse
|
(*DeleteNamespaceResponse)(nil), // 24: headscale.v1.DeleteNamespaceResponse
|
||||||
(*ListPreAuthKeysResponse)(nil), // 25: headscale.v1.ListPreAuthKeysResponse
|
(*ListNamespacesResponse)(nil), // 25: headscale.v1.ListNamespacesResponse
|
||||||
(*DebugCreateMachineResponse)(nil), // 26: headscale.v1.DebugCreateMachineResponse
|
(*CreatePreAuthKeyResponse)(nil), // 26: headscale.v1.CreatePreAuthKeyResponse
|
||||||
(*GetMachineResponse)(nil), // 27: headscale.v1.GetMachineResponse
|
(*ExpirePreAuthKeyResponse)(nil), // 27: headscale.v1.ExpirePreAuthKeyResponse
|
||||||
(*RegisterMachineResponse)(nil), // 28: headscale.v1.RegisterMachineResponse
|
(*ListPreAuthKeysResponse)(nil), // 28: headscale.v1.ListPreAuthKeysResponse
|
||||||
(*DeleteMachineResponse)(nil), // 29: headscale.v1.DeleteMachineResponse
|
(*DebugCreateMachineResponse)(nil), // 29: headscale.v1.DebugCreateMachineResponse
|
||||||
(*ExpireMachineResponse)(nil), // 30: headscale.v1.ExpireMachineResponse
|
(*GetMachineResponse)(nil), // 30: headscale.v1.GetMachineResponse
|
||||||
(*ListMachinesResponse)(nil), // 31: headscale.v1.ListMachinesResponse
|
(*RegisterMachineResponse)(nil), // 31: headscale.v1.RegisterMachineResponse
|
||||||
(*ShareMachineResponse)(nil), // 32: headscale.v1.ShareMachineResponse
|
(*DeleteMachineResponse)(nil), // 32: headscale.v1.DeleteMachineResponse
|
||||||
(*UnshareMachineResponse)(nil), // 33: headscale.v1.UnshareMachineResponse
|
(*ExpireMachineResponse)(nil), // 33: headscale.v1.ExpireMachineResponse
|
||||||
(*GetMachineRouteResponse)(nil), // 34: headscale.v1.GetMachineRouteResponse
|
(*ListMachinesResponse)(nil), // 34: headscale.v1.ListMachinesResponse
|
||||||
(*EnableMachineRoutesResponse)(nil), // 35: headscale.v1.EnableMachineRoutesResponse
|
(*ShareMachineResponse)(nil), // 35: headscale.v1.ShareMachineResponse
|
||||||
|
(*UnshareMachineResponse)(nil), // 36: headscale.v1.UnshareMachineResponse
|
||||||
|
(*GetMachineRouteResponse)(nil), // 37: headscale.v1.GetMachineRouteResponse
|
||||||
|
(*EnableMachineRoutesResponse)(nil), // 38: headscale.v1.EnableMachineRoutesResponse
|
||||||
|
(*CreateApiKeyResponse)(nil), // 39: headscale.v1.CreateApiKeyResponse
|
||||||
|
(*ExpireApiKeyResponse)(nil), // 40: headscale.v1.ExpireApiKeyResponse
|
||||||
|
(*ListApiKeysResponse)(nil), // 41: headscale.v1.ListApiKeysResponse
|
||||||
}
|
}
|
||||||
var file_headscale_v1_headscale_proto_depIdxs = []int32{
|
var file_headscale_v1_headscale_proto_depIdxs = []int32{
|
||||||
0, // 0: headscale.v1.HeadscaleService.GetNamespace:input_type -> headscale.v1.GetNamespaceRequest
|
0, // 0: headscale.v1.HeadscaleService.GetNamespace:input_type -> headscale.v1.GetNamespaceRequest
|
||||||
@@ -249,26 +279,32 @@ var file_headscale_v1_headscale_proto_depIdxs = []int32{
|
|||||||
15, // 15: headscale.v1.HeadscaleService.UnshareMachine:input_type -> headscale.v1.UnshareMachineRequest
|
15, // 15: headscale.v1.HeadscaleService.UnshareMachine:input_type -> headscale.v1.UnshareMachineRequest
|
||||||
16, // 16: headscale.v1.HeadscaleService.GetMachineRoute:input_type -> headscale.v1.GetMachineRouteRequest
|
16, // 16: headscale.v1.HeadscaleService.GetMachineRoute:input_type -> headscale.v1.GetMachineRouteRequest
|
||||||
17, // 17: headscale.v1.HeadscaleService.EnableMachineRoutes:input_type -> headscale.v1.EnableMachineRoutesRequest
|
17, // 17: headscale.v1.HeadscaleService.EnableMachineRoutes:input_type -> headscale.v1.EnableMachineRoutesRequest
|
||||||
18, // 18: headscale.v1.HeadscaleService.GetNamespace:output_type -> headscale.v1.GetNamespaceResponse
|
18, // 18: headscale.v1.HeadscaleService.CreateApiKey:input_type -> headscale.v1.CreateApiKeyRequest
|
||||||
19, // 19: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse
|
19, // 19: headscale.v1.HeadscaleService.ExpireApiKey:input_type -> headscale.v1.ExpireApiKeyRequest
|
||||||
20, // 20: headscale.v1.HeadscaleService.RenameNamespace:output_type -> headscale.v1.RenameNamespaceResponse
|
20, // 20: headscale.v1.HeadscaleService.ListApiKeys:input_type -> headscale.v1.ListApiKeysRequest
|
||||||
21, // 21: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse
|
21, // 21: headscale.v1.HeadscaleService.GetNamespace:output_type -> headscale.v1.GetNamespaceResponse
|
||||||
22, // 22: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse
|
22, // 22: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse
|
||||||
23, // 23: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
|
23, // 23: headscale.v1.HeadscaleService.RenameNamespace:output_type -> headscale.v1.RenameNamespaceResponse
|
||||||
24, // 24: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
|
24, // 24: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse
|
||||||
25, // 25: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
|
25, // 25: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse
|
||||||
26, // 26: headscale.v1.HeadscaleService.DebugCreateMachine:output_type -> headscale.v1.DebugCreateMachineResponse
|
26, // 26: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
|
||||||
27, // 27: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse
|
27, // 27: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
|
||||||
28, // 28: headscale.v1.HeadscaleService.RegisterMachine:output_type -> headscale.v1.RegisterMachineResponse
|
28, // 28: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
|
||||||
29, // 29: headscale.v1.HeadscaleService.DeleteMachine:output_type -> headscale.v1.DeleteMachineResponse
|
29, // 29: headscale.v1.HeadscaleService.DebugCreateMachine:output_type -> headscale.v1.DebugCreateMachineResponse
|
||||||
30, // 30: headscale.v1.HeadscaleService.ExpireMachine:output_type -> headscale.v1.ExpireMachineResponse
|
30, // 30: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse
|
||||||
31, // 31: headscale.v1.HeadscaleService.ListMachines:output_type -> headscale.v1.ListMachinesResponse
|
31, // 31: headscale.v1.HeadscaleService.RegisterMachine:output_type -> headscale.v1.RegisterMachineResponse
|
||||||
32, // 32: headscale.v1.HeadscaleService.ShareMachine:output_type -> headscale.v1.ShareMachineResponse
|
32, // 32: headscale.v1.HeadscaleService.DeleteMachine:output_type -> headscale.v1.DeleteMachineResponse
|
||||||
33, // 33: headscale.v1.HeadscaleService.UnshareMachine:output_type -> headscale.v1.UnshareMachineResponse
|
33, // 33: headscale.v1.HeadscaleService.ExpireMachine:output_type -> headscale.v1.ExpireMachineResponse
|
||||||
34, // 34: headscale.v1.HeadscaleService.GetMachineRoute:output_type -> headscale.v1.GetMachineRouteResponse
|
34, // 34: headscale.v1.HeadscaleService.ListMachines:output_type -> headscale.v1.ListMachinesResponse
|
||||||
35, // 35: headscale.v1.HeadscaleService.EnableMachineRoutes:output_type -> headscale.v1.EnableMachineRoutesResponse
|
35, // 35: headscale.v1.HeadscaleService.ShareMachine:output_type -> headscale.v1.ShareMachineResponse
|
||||||
18, // [18:36] is the sub-list for method output_type
|
36, // 36: headscale.v1.HeadscaleService.UnshareMachine:output_type -> headscale.v1.UnshareMachineResponse
|
||||||
0, // [0:18] is the sub-list for method input_type
|
37, // 37: headscale.v1.HeadscaleService.GetMachineRoute:output_type -> headscale.v1.GetMachineRouteResponse
|
||||||
|
38, // 38: headscale.v1.HeadscaleService.EnableMachineRoutes:output_type -> headscale.v1.EnableMachineRoutesResponse
|
||||||
|
39, // 39: headscale.v1.HeadscaleService.CreateApiKey:output_type -> headscale.v1.CreateApiKeyResponse
|
||||||
|
40, // 40: headscale.v1.HeadscaleService.ExpireApiKey:output_type -> headscale.v1.ExpireApiKeyResponse
|
||||||
|
41, // 41: headscale.v1.HeadscaleService.ListApiKeys:output_type -> headscale.v1.ListApiKeysResponse
|
||||||
|
21, // [21:42] is the sub-list for method output_type
|
||||||
|
0, // [0:21] is the sub-list for method input_type
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
0, // [0:0] is the sub-list for field type_name
|
0, // [0:0] is the sub-list for field type_name
|
||||||
@@ -283,6 +319,7 @@ func file_headscale_v1_headscale_proto_init() {
|
|||||||
file_headscale_v1_preauthkey_proto_init()
|
file_headscale_v1_preauthkey_proto_init()
|
||||||
file_headscale_v1_machine_proto_init()
|
file_headscale_v1_machine_proto_init()
|
||||||
file_headscale_v1_routes_proto_init()
|
file_headscale_v1_routes_proto_init()
|
||||||
|
file_headscale_v1_apikey_proto_init()
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
|
|||||||
@@ -891,6 +891,92 @@ func local_request_HeadscaleService_EnableMachineRoutes_0(ctx context.Context, m
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq CreateApiKeyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.CreateApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_HeadscaleService_CreateApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq CreateApiKeyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.CreateApiKey(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ExpireApiKeyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.ExpireApiKey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_HeadscaleService_ExpireApiKey_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ExpireApiKeyRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
newReader, berr := utilities.IOReaderFactory(req.Body)
|
||||||
|
if berr != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
|
||||||
|
}
|
||||||
|
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.ExpireApiKey(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, client HeadscaleServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ListApiKeysRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := client.ListApiKeys(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_HeadscaleService_ListApiKeys_0(ctx context.Context, marshaler runtime.Marshaler, server HeadscaleServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq ListApiKeysRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
msg, err := server.ListApiKeys(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux".
|
// RegisterHeadscaleServiceHandlerServer registers the http handlers for service HeadscaleService to "mux".
|
||||||
// UnaryRPC :call HeadscaleServiceServer directly.
|
// UnaryRPC :call HeadscaleServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
@@ -1311,6 +1397,75 @@ func RegisterHeadscaleServiceHandlerServer(ctx context.Context, mux *runtime.Ser
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_HeadscaleService_CreateApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_HeadscaleService_CreateApiKey_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_CreateApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_HeadscaleService_ExpireApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_HeadscaleService_ExpireApiKey_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_ExpireApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_HeadscaleService_ListApiKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_HeadscaleService_ListApiKeys_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_ListApiKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1712,6 +1867,66 @@ func RegisterHeadscaleServiceHandlerClient(ctx context.Context, mux *runtime.Ser
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_HeadscaleService_CreateApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/CreateApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_HeadscaleService_CreateApiKey_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_CreateApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_HeadscaleService_ExpireApiKey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ExpireApiKey", runtime.WithHTTPPathPattern("/api/v1/apikey/expire"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_HeadscaleService_ExpireApiKey_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_ExpireApiKey_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_HeadscaleService_ListApiKeys_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/headscale.v1.HeadscaleService/ListApiKeys", runtime.WithHTTPPathPattern("/api/v1/apikey"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_HeadscaleService_ListApiKeys_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_HeadscaleService_ListApiKeys_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1751,6 +1966,12 @@ var (
|
|||||||
pattern_HeadscaleService_GetMachineRoute_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, ""))
|
pattern_HeadscaleService_GetMachineRoute_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, ""))
|
||||||
|
|
||||||
pattern_HeadscaleService_EnableMachineRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, ""))
|
pattern_HeadscaleService_EnableMachineRoutes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "machine", "machine_id", "routes"}, ""))
|
||||||
|
|
||||||
|
pattern_HeadscaleService_CreateApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, ""))
|
||||||
|
|
||||||
|
pattern_HeadscaleService_ExpireApiKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "apikey", "expire"}, ""))
|
||||||
|
|
||||||
|
pattern_HeadscaleService_ListApiKeys_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "apikey"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1789,4 +2010,10 @@ var (
|
|||||||
forward_HeadscaleService_GetMachineRoute_0 = runtime.ForwardResponseMessage
|
forward_HeadscaleService_GetMachineRoute_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_HeadscaleService_EnableMachineRoutes_0 = runtime.ForwardResponseMessage
|
forward_HeadscaleService_EnableMachineRoutes_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_HeadscaleService_CreateApiKey_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_HeadscaleService_ExpireApiKey_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_HeadscaleService_ListApiKeys_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
||||||
|
|
||||||
grpc "google.golang.org/grpc"
|
grpc "google.golang.org/grpc"
|
||||||
codes "google.golang.org/grpc/codes"
|
codes "google.golang.org/grpc/codes"
|
||||||
status "google.golang.org/grpc/status"
|
status "google.golang.org/grpc/status"
|
||||||
@@ -40,6 +41,10 @@ type HeadscaleServiceClient interface {
|
|||||||
// --- Route start ---
|
// --- Route start ---
|
||||||
GetMachineRoute(ctx context.Context, in *GetMachineRouteRequest, opts ...grpc.CallOption) (*GetMachineRouteResponse, error)
|
GetMachineRoute(ctx context.Context, in *GetMachineRouteRequest, opts ...grpc.CallOption) (*GetMachineRouteResponse, error)
|
||||||
EnableMachineRoutes(ctx context.Context, in *EnableMachineRoutesRequest, opts ...grpc.CallOption) (*EnableMachineRoutesResponse, error)
|
EnableMachineRoutes(ctx context.Context, in *EnableMachineRoutesRequest, opts ...grpc.CallOption) (*EnableMachineRoutesResponse, error)
|
||||||
|
// --- ApiKeys start ---
|
||||||
|
CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error)
|
||||||
|
ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error)
|
||||||
|
ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headscaleServiceClient struct {
|
type headscaleServiceClient struct {
|
||||||
@@ -212,6 +217,33 @@ func (c *headscaleServiceClient) EnableMachineRoutes(ctx context.Context, in *En
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) {
|
||||||
|
out := new(CreateApiKeyResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateApiKey", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) {
|
||||||
|
out := new(ExpireApiKeyResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireApiKey", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) {
|
||||||
|
out := new(ListApiKeysResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListApiKeys", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HeadscaleServiceServer is the server API for HeadscaleService service.
|
// HeadscaleServiceServer is the server API for HeadscaleService service.
|
||||||
// All implementations must embed UnimplementedHeadscaleServiceServer
|
// All implementations must embed UnimplementedHeadscaleServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -238,6 +270,10 @@ type HeadscaleServiceServer interface {
|
|||||||
// --- Route start ---
|
// --- Route start ---
|
||||||
GetMachineRoute(context.Context, *GetMachineRouteRequest) (*GetMachineRouteResponse, error)
|
GetMachineRoute(context.Context, *GetMachineRouteRequest) (*GetMachineRouteResponse, error)
|
||||||
EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error)
|
EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error)
|
||||||
|
// --- ApiKeys start ---
|
||||||
|
CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error)
|
||||||
|
ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error)
|
||||||
|
ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error)
|
||||||
mustEmbedUnimplementedHeadscaleServiceServer()
|
mustEmbedUnimplementedHeadscaleServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +335,15 @@ func (UnimplementedHeadscaleServiceServer) GetMachineRoute(context.Context, *Get
|
|||||||
func (UnimplementedHeadscaleServiceServer) EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error) {
|
func (UnimplementedHeadscaleServiceServer) EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method EnableMachineRoutes not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method EnableMachineRoutes not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) CreateApiKey(context.Context, *CreateApiKeyRequest) (*CreateApiKeyResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreateApiKey not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ExpireApiKey(context.Context, *ExpireApiKeyRequest) (*ExpireApiKeyResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ExpireApiKey not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ListApiKeys(context.Context, *ListApiKeysRequest) (*ListApiKeysResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListApiKeys not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
||||||
|
|
||||||
// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -636,6 +681,60 @@ func _HeadscaleService_EnableMachineRoutes_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_CreateApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CreateApiKeyRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).CreateApiKey(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/CreateApiKey",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).CreateApiKey(ctx, req.(*CreateApiKeyRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ExpireApiKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ExpireApiKeyRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ExpireApiKey",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, req.(*ExpireApiKeyRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ListApiKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListApiKeysRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ListApiKeys(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ListApiKeys",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ListApiKeys(ctx, req.(*ListApiKeysRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
|
// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -715,6 +814,18 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "EnableMachineRoutes",
|
MethodName: "EnableMachineRoutes",
|
||||||
Handler: _HeadscaleService_EnableMachineRoutes_Handler,
|
Handler: _HeadscaleService_EnableMachineRoutes_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CreateApiKey",
|
||||||
|
Handler: _HeadscaleService_CreateApiKey_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ExpireApiKey",
|
||||||
|
Handler: _HeadscaleService_ExpireApiKey_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListApiKeys",
|
||||||
|
Handler: _HeadscaleService_ListApiKeys_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "headscale/v1/headscale.proto",
|
Metadata: "headscale/v1/headscale.proto",
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/machine.proto
|
// source: headscale/v1/machine.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -82,7 +83,7 @@ type Machine struct {
|
|||||||
MachineKey string `protobuf:"bytes,2,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"`
|
MachineKey string `protobuf:"bytes,2,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"`
|
||||||
NodeKey string `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"`
|
NodeKey string `protobuf:"bytes,3,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"`
|
||||||
DiscoKey string `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"`
|
DiscoKey string `protobuf:"bytes,4,opt,name=disco_key,json=discoKey,proto3" json:"disco_key,omitempty"`
|
||||||
IpAddress string `protobuf:"bytes,5,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,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"`
|
Registered bool `protobuf:"varint,8,opt,name=registered,proto3" json:"registered,omitempty"`
|
||||||
@@ -154,11 +155,11 @@ func (x *Machine) GetDiscoKey() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Machine) GetIpAddress() string {
|
func (x *Machine) GetIpAddresses() []string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.IpAddress
|
return x.IpAddresses
|
||||||
}
|
}
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Machine) GetName() string {
|
func (x *Machine) GetName() string {
|
||||||
@@ -1026,129 +1027,129 @@ 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, 0xf9, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63,
|
0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 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,
|
||||||
0x6e, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65,
|
0x6e, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65,
|
||||||
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79,
|
0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79,
|
||||||
0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
0x12, 0x1b, 0x0a, 0x09, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a,
|
0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a,
|
||||||
0x0a, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28,
|
0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x05, 0x20,
|
||||||
0x09, 0x52, 0x09, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04,
|
0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73,
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||||
0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20,
|
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61,
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||||
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73,
|
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72,
|
||||||
0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x67,
|
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72, 0x65, 0x67, 0x69, 0x73,
|
0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x72,
|
||||||
0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e,
|
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09,
|
||||||
0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e,
|
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
|
||||||
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x37,
|
0x6f, 0x64, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68,
|
||||||
0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
0x6f, 0x64, 0x12, 0x37, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18,
|
||||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6c,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||||
0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c, 0x61, 0x73, 0x74, 0x5f,
|
0x70, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x50, 0x0a, 0x16, 0x6c,
|
||||||
0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74,
|
0x61, 0x73, 0x74, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x5f, 0x75,
|
||||||
0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||||
0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
|
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x14, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x75, 0x63,
|
||||||
0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70,
|
0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a,
|
||||||
0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
|
||||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x3a, 0x0a,
|
|
||||||
0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0d, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
|
||||||
0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x70,
|
|
||||||
0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65,
|
|
||||||
0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
|
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72,
|
||||||
0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
|
0x79, 0x12, 0x3a, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65,
|
||||||
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c,
|
0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
|
||||||
0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
0x79, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a,
|
||||||
0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x4a,
|
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28,
|
||||||
0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||||
|
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63,
|
||||||
|
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69,
|
||||||
|
0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||||
|
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
|
||||||
|
0x65, 0x79, 0x22, 0x4a, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a,
|
||||||
|
0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32,
|
||||||
|
0x0a, 0x11, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||||
|
0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69,
|
||||||
|
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x49, 0x64, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68,
|
||||||
|
0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c,
|
||||||
|
0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
|
0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70,
|
||||||
|
0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
|
0x22, 0x48, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63,
|
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63,
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61,
|
0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61,
|
||||||
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x32, 0x0a, 0x11, 0x47, 0x65,
|
0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69,
|
||||||
0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x45,
|
|
||||||
0x0a, 0x12, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70,
|
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18,
|
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
|
||||||
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61,
|
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d,
|
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
|
|
||||||
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15,
|
|
||||||
0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73,
|
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d,
|
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
|
|
||||||
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x15,
|
|
||||||
0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73,
|
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
|
||||||
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d,
|
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61,
|
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a,
|
|
||||||
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
|
||||||
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x14, 0x4c,
|
|
||||||
0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
|
||||||
0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x18,
|
|
||||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
|
||||||
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x6d, 0x61,
|
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a,
|
|
||||||
0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
|
||||||
0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09,
|
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
|
||||||
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47, 0x0a, 0x14, 0x53, 0x68,
|
|
||||||
0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
|
||||||
0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20,
|
|
||||||
0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
|
||||||
0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68,
|
|
||||||
0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61,
|
|
||||||
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a,
|
|
||||||
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
|
|
||||||
0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
|
|
||||||
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a, 0x16, 0x55, 0x6e, 0x73,
|
|
||||||
0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
|
||||||
0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
|
||||||
0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63,
|
|
||||||
0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65,
|
|
||||||
0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
|
||||||
0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01,
|
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, 0x12,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22,
|
||||||
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
|
0x49, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x52,
|
||||||
0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18,
|
0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x4d, 0x0a,
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68,
|
0x52, 0x08, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x52, 0x0a, 0x13, 0x53, 0x68,
|
||||||
0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d,
|
0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68,
|
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||||
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68,
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2a, 0x82, 0x01, 0x0a,
|
0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20,
|
||||||
0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
|
0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x47,
|
||||||
0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48,
|
0x0a, 0x14, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||||
0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54,
|
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x17,
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07,
|
||||||
0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f,
|
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x54, 0x0a, 0x15, 0x55, 0x6e, 0x73, 0x68, 0x61,
|
||||||
0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x47, 0x49, 0x53,
|
0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10,
|
0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
||||||
0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12,
|
||||||
0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72,
|
0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x49, 0x0a,
|
||||||
0x6f, 0x74, 0x6f, 0x33,
|
0x16, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
|
0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
|
||||||
|
0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x77, 0x0a, 0x19, 0x44, 0x65, 0x62, 0x75,
|
||||||
|
0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
|
0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75,
|
||||||
|
0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65,
|
||||||
|
0x73, 0x22, 0x4d, 0x0a, 0x1a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
|
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||||
|
0x2f, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||||
|
0x32, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e,
|
||||||
|
0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x2a, 0x82, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x74,
|
||||||
|
0x68, 0x6f, 0x64, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f,
|
||||||
|
0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
|
||||||
|
0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52,
|
||||||
|
0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4b, 0x45, 0x59,
|
||||||
|
0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d,
|
||||||
|
0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x52,
|
||||||
|
0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f,
|
||||||
|
0x49, 0x44, 0x43, 0x10, 0x03, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||||
|
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/namespace.proto
|
// source: headscale/v1/namespace.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/preauthkey.proto
|
// source: headscale/v1/preauthkey.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.18.1
|
// protoc (unknown)
|
||||||
// source: headscale/v1/routes.proto
|
// source: headscale/v1/routes.proto
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
43
gen/openapiv2/headscale/v1/apikey.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/apikey.swagger.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/apikey.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,91 @@
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/api/v1/apikey": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HeadscaleService_ListApiKeys",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ListApiKeysResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"summary": "--- ApiKeys start ---",
|
||||||
|
"operationId": "HeadscaleService_CreateApiKey",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreateApiKeyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreateApiKeyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/apikey/expire": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_ExpireApiKey",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ExpireApiKeyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ExpireApiKeyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/debug/machine": {
|
"/api/v1/debug/machine": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "--- Machine start ---",
|
"summary": "--- Machine start ---",
|
||||||
@@ -596,6 +681,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1ApiKey": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"prefix": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"lastSeen": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreateApiKeyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expiration": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreateApiKeyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKey": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1CreateNamespaceRequest": {
|
"v1CreateNamespaceRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -680,6 +806,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1ExpireApiKeyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"prefix": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ExpireApiKeyResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"v1ExpireMachineResponse": {
|
"v1ExpireMachineResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -726,6 +863,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1ListApiKeysResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKeys": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1ApiKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1ListMachinesResponse": {
|
"v1ListMachinesResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -775,8 +923,11 @@
|
|||||||
"discoKey": {
|
"discoKey": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"ipAddress": {
|
"ipAddresses": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
99
go.mod
99
go.mod
@@ -7,84 +7,85 @@ require (
|
|||||||
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
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.7
|
||||||
github.com/gofrs/uuid v4.1.0+incompatible
|
github.com/gofrs/uuid v4.2.0+incompatible
|
||||||
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.6.0
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3
|
||||||
github.com/infobloxopen/protoc-gen-gorm v1.0.1
|
github.com/infobloxopen/protoc-gen-gorm v1.1.0
|
||||||
github.com/klauspost/compress v1.13.6
|
github.com/klauspost/compress v1.14.2
|
||||||
github.com/ory/dockertest/v3 v3.7.0
|
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.11.0
|
github.com/prometheus/client_golang v1.12.1
|
||||||
github.com/pterm/pterm v0.12.30
|
github.com/pterm/pterm v0.12.36
|
||||||
github.com/rs/zerolog v1.26.0
|
github.com/rs/zerolog v1.26.1
|
||||||
github.com/soheilhy/cmux v0.1.5
|
github.com/soheilhy/cmux v0.1.5
|
||||||
github.com/spf13/cobra v1.2.1
|
github.com/spf13/cobra v1.3.0
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.10.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tailscale/hujson v0.0.0-20210923003652-c3758b31534b
|
github.com/tailscale/hujson v0.0.0-20211215203138-ffd971c5f362
|
||||||
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-20211117183948-ae814b36b871
|
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||||
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-20211104193956-4c6863e31247
|
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336
|
||||||
google.golang.org/grpc v1.42.0
|
google.golang.org/grpc v1.44.0
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.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.2
|
gorm.io/datatypes v1.0.5
|
||||||
gorm.io/driver/postgres v1.1.1
|
gorm.io/driver/postgres v1.2.3
|
||||||
gorm.io/driver/sqlite v1.1.5
|
gorm.io/driver/sqlite v1.2.6
|
||||||
gorm.io/gorm v1.21.15
|
gorm.io/gorm v1.22.5
|
||||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
||||||
tailscale.com v1.18.1
|
tailscale.com v1.20.4
|
||||||
)
|
)
|
||||||
|
|
||||||
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.0 // indirect
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // 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.1 // 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.1.0 // 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/docker/cli v20.10.8+incompatible // indirect
|
github.com/docker/cli v20.10.12+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.8+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.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // 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.9.0 // indirect
|
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
github.com/golang/glog v1.0.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/gookit/color v1.4.2 // indirect
|
github.com/gookit/color v1.5.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.2.0 // indirect
|
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
github.com/jackc/pgconn v1.10.0 // indirect
|
github.com/jackc/pgconn v1.11.0 // indirect
|
||||||
github.com/jackc/pgio v1.0.0 // indirect
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
github.com/jackc/pgtype v1.8.1 // indirect
|
github.com/jackc/pgtype v1.10.0 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
github.com/jackc/pgx/v4 v4.15.0 // indirect
|
||||||
github.com/jinzhu/gorm v1.9.16 // indirect
|
github.com/jinzhu/gorm v1.9.16 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.2 // indirect
|
github.com/jinzhu/now v1.1.4 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
@@ -92,30 +93,30 @@ require (
|
|||||||
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.5 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.8 // 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.8 // 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-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.1 // 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
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.0.3 // indirect
|
github.com/opencontainers/runc v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.2.0 // indirect
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
github.com/prometheus/common v0.32.1 // indirect
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
github.com/prometheus/procfs v0.7.3 // indirect
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/spf13/afero v1.8.1 // indirect
|
||||||
github.com/spf13/cast v1.3.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
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
|
||||||
@@ -127,12 +128,14 @@ 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-20211112202133-69e39bad7dc2 // indirect
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // 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
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // 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
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
gorm.io/driver/mysql v1.2.3 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
56
grpcv1.go
56
grpcv1.go
@@ -349,6 +349,62 @@ func (api headscaleV1APIServer) EnableMachineRoutes(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) CreateApiKey(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.CreateApiKeyRequest,
|
||||||
|
) (*v1.CreateApiKeyResponse, error) {
|
||||||
|
var expiration time.Time
|
||||||
|
if request.GetExpiration() != nil {
|
||||||
|
expiration = request.GetExpiration().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey, _, err := api.h.CreateAPIKey(
|
||||||
|
&expiration,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.CreateApiKeyResponse{ApiKey: apiKey}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ExpireApiKey(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ExpireApiKeyRequest,
|
||||||
|
) (*v1.ExpireApiKeyResponse, error) {
|
||||||
|
var apiKey *APIKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
apiKey, err = api.h.GetAPIKey(request.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.ExpireAPIKey(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ExpireApiKeyResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ListApiKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ListApiKeysRequest,
|
||||||
|
) (*v1.ListApiKeysResponse, error) {
|
||||||
|
apiKeys, err := api.h.ListAPIKeys()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]*v1.ApiKey, len(apiKeys))
|
||||||
|
for index, key := range apiKeys {
|
||||||
|
response[index] = key.toProto()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ListApiKeysResponse{ApiKeys: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// The following service calls are for testing and debugging
|
// The following service calls are for testing and debugging
|
||||||
func (api headscaleV1APIServer) DebugCreateMachine(
|
func (api headscaleV1APIServer) DebugCreateMachine(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|||||||
@@ -1193,3 +1193,148 @@ func (s *IntegrationCLITestSuite) TestRouteCommand() {
|
|||||||
"route (route-machine) is not available on node",
|
"route (route-machine) is not available on node",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationCLITestSuite) TestApiKeyCommand() {
|
||||||
|
count := 5
|
||||||
|
|
||||||
|
keys := make([]string, count)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
apiResult, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"apikeys",
|
||||||
|
"create",
|
||||||
|
"--expiration",
|
||||||
|
"24h",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
assert.NotEmpty(s.T(), apiResult)
|
||||||
|
|
||||||
|
// var apiKey v1.ApiKey
|
||||||
|
// err = json.Unmarshal([]byte(apiResult), &apiKey)
|
||||||
|
// assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
keys[i] = apiResult
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(s.T(), keys, 5)
|
||||||
|
|
||||||
|
// Test list of keys
|
||||||
|
listResult, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"apikeys",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
var listedApiKeys []v1.ApiKey
|
||||||
|
err = json.Unmarshal([]byte(listResult), &listedApiKeys)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
assert.Len(s.T(), listedApiKeys, 5)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), uint64(1), listedApiKeys[0].Id)
|
||||||
|
assert.Equal(s.T(), uint64(2), listedApiKeys[1].Id)
|
||||||
|
assert.Equal(s.T(), uint64(3), listedApiKeys[2].Id)
|
||||||
|
assert.Equal(s.T(), uint64(4), listedApiKeys[3].Id)
|
||||||
|
assert.Equal(s.T(), uint64(5), listedApiKeys[4].Id)
|
||||||
|
|
||||||
|
assert.NotEmpty(s.T(), listedApiKeys[0].Prefix)
|
||||||
|
assert.NotEmpty(s.T(), listedApiKeys[1].Prefix)
|
||||||
|
assert.NotEmpty(s.T(), listedApiKeys[2].Prefix)
|
||||||
|
assert.NotEmpty(s.T(), listedApiKeys[3].Prefix)
|
||||||
|
assert.NotEmpty(s.T(), listedApiKeys[4].Prefix)
|
||||||
|
|
||||||
|
assert.True(s.T(), listedApiKeys[0].Expiration.AsTime().After(time.Now()))
|
||||||
|
assert.True(s.T(), listedApiKeys[1].Expiration.AsTime().After(time.Now()))
|
||||||
|
assert.True(s.T(), listedApiKeys[2].Expiration.AsTime().After(time.Now()))
|
||||||
|
assert.True(s.T(), listedApiKeys[3].Expiration.AsTime().After(time.Now()))
|
||||||
|
assert.True(s.T(), listedApiKeys[4].Expiration.AsTime().After(time.Now()))
|
||||||
|
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedApiKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedApiKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedApiKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedApiKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedApiKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)),
|
||||||
|
)
|
||||||
|
|
||||||
|
expiredPrefixes := make(map[string]bool)
|
||||||
|
|
||||||
|
// Expire three keys
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
_, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"apikeys",
|
||||||
|
"expire",
|
||||||
|
"--prefix",
|
||||||
|
listedApiKeys[i].Prefix,
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
expiredPrefixes[listedApiKeys[i].Prefix] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test list pre auth keys after expire
|
||||||
|
listAfterExpireResult, err := ExecuteCommand(
|
||||||
|
&s.headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"apikeys",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
var listedAfterExpireApiKeys []v1.ApiKey
|
||||||
|
err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpireApiKeys)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
for index := range listedAfterExpireApiKeys {
|
||||||
|
if _, ok := expiredPrefixes[listedAfterExpireApiKeys[index].Prefix]; ok {
|
||||||
|
// Expired
|
||||||
|
assert.True(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Not expired
|
||||||
|
assert.False(
|
||||||
|
s.T(),
|
||||||
|
listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,20 +10,47 @@ import (
|
|||||||
|
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
||||||
|
|
||||||
|
var (
|
||||||
|
IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
|
||||||
|
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExecuteCommandConfig struct {
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecuteCommandOption func(*ExecuteCommandConfig) error
|
||||||
|
|
||||||
|
func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption {
|
||||||
|
return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error {
|
||||||
|
conf.timeout = timeout
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func ExecuteCommand(
|
func ExecuteCommand(
|
||||||
resource *dockertest.Resource,
|
resource *dockertest.Resource,
|
||||||
cmd []string,
|
cmd []string,
|
||||||
env []string,
|
env []string,
|
||||||
|
options ...ExecuteCommandOption,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
// TODO(kradalby): Make configurable
|
execConfig := ExecuteCommandConfig{
|
||||||
timeout := DOCKER_EXECUTE_TIMEOUT
|
timeout: DOCKER_EXECUTE_TIMEOUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
if err := opt(&execConfig); err != nil {
|
||||||
|
return "", fmt.Errorf("execute-command/options: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
exitCode int
|
exitCode int
|
||||||
@@ -62,16 +89,33 @@ func ExecuteCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return stdout.String(), nil
|
return stdout.String(), nil
|
||||||
case <-time.After(timeout):
|
case <-time.After(execConfig.timeout):
|
||||||
|
|
||||||
return "", fmt.Errorf("command timed out after %s", timeout)
|
return "", fmt.Errorf("command timed out after %s", execConfig.timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DockerRestartPolicy(config *docker.HostConfig) {
|
func DockerRestartPolicy(config *docker.HostConfig) {
|
||||||
// set AutoRemove to true so that stopped container goes away by itself
|
// set AutoRemove to true so that stopped container goes away by itself on error *immediately*.
|
||||||
config.AutoRemove = true
|
// when set to false, containers remain until the end of the integration test.
|
||||||
|
config.AutoRemove = false
|
||||||
config.RestartPolicy = docker.RestartPolicy{
|
config.RestartPolicy = docker.RestartPolicy{
|
||||||
Name: "no",
|
Name: "no",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DockerAllowLocalIPv6(config *docker.HostConfig) {
|
||||||
|
if config.Sysctls == nil {
|
||||||
|
config.Sysctls = make(map[string]string, 1)
|
||||||
|
}
|
||||||
|
config.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func DockerAllowNetworkAdministration(config *docker.HostConfig) {
|
||||||
|
config.CapAdd = append(config.CapAdd, "NET_ADMIN")
|
||||||
|
config.Mounts = append(config.Mounts, docker.HostMount{
|
||||||
|
Type: "bind",
|
||||||
|
Source: "/dev/net/tun",
|
||||||
|
Target: "/dev/net/tun",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tailscaleVersions = []string{"1.18.1", "1.16.2", "1.14.3", "1.12.3"}
|
var tailscaleVersions = []string{"1.20.4", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
|
||||||
|
|
||||||
type TestNamespace struct {
|
type TestNamespace struct {
|
||||||
count int
|
count int
|
||||||
@@ -164,9 +164,7 @@ func (s *IntegrationTestSuite) tailscaleContainer(
|
|||||||
Name: hostname,
|
Name: hostname,
|
||||||
Networks: []*dockertest.Network{&s.network},
|
Networks: []*dockertest.Network{&s.network},
|
||||||
Cmd: []string{
|
Cmd: []string{
|
||||||
"tailscaled",
|
"tailscaled", "--tun=tsdev",
|
||||||
"--tun=userspace-networking",
|
|
||||||
"--socks5-server=localhost:1055",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +172,8 @@ func (s *IntegrationTestSuite) tailscaleContainer(
|
|||||||
tailscaleBuildOptions,
|
tailscaleBuildOptions,
|
||||||
tailscaleOptions,
|
tailscaleOptions,
|
||||||
DockerRestartPolicy,
|
DockerRestartPolicy,
|
||||||
|
DockerAllowLocalIPv6,
|
||||||
|
DockerAllowNetworkAdministration,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not start resource: %s", err)
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
@@ -372,70 +372,74 @@ func (s *IntegrationTestSuite) TestListNodes() {
|
|||||||
|
|
||||||
func (s *IntegrationTestSuite) TestGetIpAddresses() {
|
func (s *IntegrationTestSuite) TestGetIpAddresses() {
|
||||||
for _, scales := range s.namespaces {
|
for _, scales := range s.namespaces {
|
||||||
ipPrefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
|
|
||||||
ips, err := getIPs(scales.tailscales)
|
ips, err := getIPs(scales.tailscales)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
for hostname := range scales.tailscales {
|
for hostname := range scales.tailscales {
|
||||||
s.T().Run(hostname, func(t *testing.T) {
|
ips := ips[hostname]
|
||||||
ip, ok := ips[hostname]
|
for _, ip := range ips {
|
||||||
|
s.T().Run(hostname, func(t *testing.T) {
|
||||||
|
assert.NotNil(t, ip)
|
||||||
|
|
||||||
assert.True(t, ok)
|
fmt.Printf("IP for %s: %s\n", hostname, ip)
|
||||||
assert.NotNil(t, ip)
|
|
||||||
|
|
||||||
fmt.Printf("IP for %s: %s\n", hostname, ip)
|
// c.Assert(ip.Valid(), check.IsTrue)
|
||||||
|
assert.True(t, ip.Is4() || ip.Is6())
|
||||||
// c.Assert(ip.Valid(), check.IsTrue)
|
switch {
|
||||||
assert.True(t, ip.Is4())
|
case ip.Is4():
|
||||||
assert.True(t, ipPrefix.Contains(ip))
|
assert.True(t, IpPrefix4.Contains(ip))
|
||||||
})
|
case ip.Is6():
|
||||||
|
assert.True(t, IpPrefix6.Contains(ip))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kradalby): fix this test
|
// TODO(kradalby): fix this test
|
||||||
// We need some way to impot ipnstate.Status from multiple go packages.
|
// We need some way to import ipnstate.Status from multiple go packages.
|
||||||
// Currently it will only work with 1.18.x since that is the last
|
// Currently it will only work with 1.18.x since that is the last
|
||||||
// version we have in go.mod
|
// version we have in go.mod
|
||||||
// func (s *IntegrationTestSuite) TestStatus() {
|
// func (s *IntegrationTestSuite) TestStatus() {
|
||||||
// for _, scales := range s.namespaces {
|
// for _, scales := range s.namespaces {
|
||||||
// ips, err := getIPs(scales.tailscales)
|
// ips, err := getIPs(scales.tailscales)
|
||||||
// assert.Nil(s.T(), err)
|
// assert.Nil(s.T(), err)
|
||||||
//
|
//
|
||||||
// for hostname, tailscale := range scales.tailscales {
|
// for hostname, tailscale := range scales.tailscales {
|
||||||
// s.T().Run(hostname, func(t *testing.T) {
|
// s.T().Run(hostname, func(t *testing.T) {
|
||||||
// command := []string{"tailscale", "status", "--json"}
|
// command := []string{"tailscale", "status", "--json"}
|
||||||
//
|
//
|
||||||
// fmt.Printf("Getting status for %s\n", hostname)
|
// fmt.Printf("Getting status for %s\n", hostname)
|
||||||
// result, err := ExecuteCommand(
|
// result, err := ExecuteCommand(
|
||||||
// &tailscale,
|
// &tailscale,
|
||||||
// command,
|
// command,
|
||||||
// []string{},
|
// []string{},
|
||||||
// )
|
// )
|
||||||
// assert.Nil(t, err)
|
// assert.Nil(t, err)
|
||||||
//
|
//
|
||||||
// var status ipnstate.Status
|
// var status ipnstate.Status
|
||||||
// err = json.Unmarshal([]byte(result), &status)
|
// err = json.Unmarshal([]byte(result), &status)
|
||||||
// assert.Nil(s.T(), err)
|
// assert.Nil(s.T(), err)
|
||||||
//
|
//
|
||||||
// // TODO(kradalby): Replace this check with peer length of SAME namespace
|
// // TODO(kradalby): Replace this check with peer length of SAME namespace
|
||||||
// // Check if we have as many nodes in status
|
// // Check if we have as many nodes in status
|
||||||
// // as we have IPs/tailscales
|
// // as we have IPs/tailscales
|
||||||
// // lines := strings.Split(result, "\n")
|
// // lines := strings.Split(result, "\n")
|
||||||
// // assert.Equal(t, len(ips), len(lines)-1)
|
// // assert.Equal(t, len(ips), len(lines)-1)
|
||||||
// // assert.Equal(t, len(scales.tailscales), len(lines)-1)
|
// // assert.Equal(t, len(scales.tailscales), len(lines)-1)
|
||||||
//
|
//
|
||||||
// peerIps := getIPsfromIPNstate(status)
|
// peerIps := getIPsfromIPNstate(status)
|
||||||
//
|
//
|
||||||
// // Check that all hosts is present in all hosts status
|
// // Check that all hosts is present in all hosts status
|
||||||
// for ipHostname, ip := range ips {
|
// for ipHostname, ip := range ips {
|
||||||
// if hostname != ipHostname {
|
// if hostname != ipHostname {
|
||||||
// assert.Contains(t, peerIps, ip)
|
// assert.Contains(t, peerIps, ip)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
||||||
@@ -448,43 +452,46 @@ func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
|||||||
return ips
|
return ips
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestPingAllPeers() {
|
func (s *IntegrationTestSuite) TestPingAllPeersByAddress() {
|
||||||
for _, scales := range s.namespaces {
|
for _, scales := range s.namespaces {
|
||||||
ips, err := getIPs(scales.tailscales)
|
ips, err := getIPs(scales.tailscales)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
for peername, ip := range ips {
|
for peername, peerIPs := range ips {
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
for i, ip := range peerIPs {
|
||||||
// We currently cant ping ourselves, so skip that.
|
// We currently cant ping ourselves, so skip that.
|
||||||
if peername != hostname {
|
if peername == hostname {
|
||||||
// We are only interested in "direct ping" which means what we
|
continue
|
||||||
// might need a couple of more attempts before reaching the node.
|
|
||||||
command := []string{
|
|
||||||
"tailscale", "ping",
|
|
||||||
"--timeout=1s",
|
|
||||||
"--c=10",
|
|
||||||
"--until-direct=true",
|
|
||||||
ip.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(
|
|
||||||
"Pinging from %s (%s) to %s (%s)\n",
|
|
||||||
hostname,
|
|
||||||
ips[hostname],
|
|
||||||
peername,
|
|
||||||
ip,
|
|
||||||
)
|
|
||||||
result, err := ExecuteCommand(
|
|
||||||
&tailscale,
|
|
||||||
command,
|
|
||||||
[]string{},
|
|
||||||
)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fmt.Printf("Result for %s: %s\n", hostname, result)
|
|
||||||
assert.Contains(t, result, "pong")
|
|
||||||
}
|
}
|
||||||
})
|
s.T().
|
||||||
|
Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) {
|
||||||
|
// We are only interested in "direct ping" which means what we
|
||||||
|
// might need a couple of more attempts before reaching the node.
|
||||||
|
command := []string{
|
||||||
|
"tailscale", "ping",
|
||||||
|
"--timeout=1s",
|
||||||
|
"--c=10",
|
||||||
|
"--until-direct=true",
|
||||||
|
ip.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"Pinging from %s to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Printf("Result for %s: %s\n", hostname, result)
|
||||||
|
assert.Contains(t, result, "pong")
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,44 +560,44 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
|||||||
// TODO(juanfont): We have to find out why do we need to wait
|
// TODO(juanfont): We have to find out why do we need to wait
|
||||||
time.Sleep(100 * time.Second) // Wait for the nodes to receive updates
|
time.Sleep(100 * time.Second) // Wait for the nodes to receive updates
|
||||||
|
|
||||||
mainIps, err := getIPs(main.tailscales)
|
|
||||||
assert.Nil(s.T(), err)
|
|
||||||
|
|
||||||
sharedIps, err := getIPs(shared.tailscales)
|
sharedIps, err := getIPs(shared.tailscales)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
for hostname, tailscale := range main.tailscales {
|
for hostname, tailscale := range main.tailscales {
|
||||||
for peername, ip := range sharedIps {
|
for peername, peerIPs := range sharedIps {
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
for i, ip := range peerIPs {
|
||||||
// We currently cant ping ourselves, so skip that.
|
// We currently cant ping ourselves, so skip that.
|
||||||
if peername != hostname {
|
if peername == hostname {
|
||||||
// We are only interested in "direct ping" which means what we
|
continue
|
||||||
// might need a couple of more attempts before reaching the node.
|
|
||||||
command := []string{
|
|
||||||
"tailscale", "ping",
|
|
||||||
"--timeout=15s",
|
|
||||||
"--c=20",
|
|
||||||
"--until-direct=true",
|
|
||||||
ip.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(
|
|
||||||
"Pinging from %s (%s) to %s (%s)\n",
|
|
||||||
hostname,
|
|
||||||
mainIps[hostname],
|
|
||||||
peername,
|
|
||||||
ip,
|
|
||||||
)
|
|
||||||
result, err := ExecuteCommand(
|
|
||||||
&tailscale,
|
|
||||||
command,
|
|
||||||
[]string{},
|
|
||||||
)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fmt.Printf("Result for %s: %s\n", hostname, result)
|
|
||||||
assert.Contains(t, result, "pong")
|
|
||||||
}
|
}
|
||||||
})
|
s.T().
|
||||||
|
Run(fmt.Sprintf("%s-%s-%d", hostname, peername, i), func(t *testing.T) {
|
||||||
|
// We are only interested in "direct ping" which means what we
|
||||||
|
// might need a couple of more attempts before reaching the node.
|
||||||
|
command := []string{
|
||||||
|
"tailscale", "ping",
|
||||||
|
"--timeout=15s",
|
||||||
|
"--c=20",
|
||||||
|
"--until-direct=true",
|
||||||
|
ip.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"Pinging from %s to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Printf("Result for %s: %s\n", hostname, result)
|
||||||
|
assert.Contains(t, result, "pong")
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -599,9 +606,19 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
for _, scales := range s.namespaces {
|
for _, scales := range s.namespaces {
|
||||||
ips, err := getIPs(scales.tailscales)
|
ips, err := getIPs(scales.tailscales)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
apiURLs, err := getAPIURLs(scales.tailscales)
|
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
retry := func(times int, sleepInverval time.Duration, doWork func() error) (err error) {
|
||||||
|
for attempts := 0; attempts < times; attempts++ {
|
||||||
|
err = doWork()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(sleepInverval)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
|
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
|
||||||
_, err := ExecuteCommand(
|
_, err := ExecuteCommand(
|
||||||
@@ -610,63 +627,31 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
for peername, ip := range ips {
|
for peername := range ips {
|
||||||
|
if peername == hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||||
if peername != hostname {
|
command := []string{
|
||||||
// Under normal circumstances, we should be able to send a file
|
"tailscale", "file", "cp",
|
||||||
// using `tailscale file cp` - but not in userspace networking mode
|
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
||||||
// So curl!
|
fmt.Sprintf("%s:", peername),
|
||||||
peerAPI, ok := apiURLs[ip]
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
// TODO(juanfont): We still have some issues with the test infrastructure, so
|
|
||||||
// lets run curl multiple times until it works.
|
|
||||||
attempts := 0
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
command := []string{
|
|
||||||
"curl",
|
|
||||||
"--retry-connrefused",
|
|
||||||
"--retry-delay",
|
|
||||||
"30",
|
|
||||||
"--retry",
|
|
||||||
"10",
|
|
||||||
"--connect-timeout",
|
|
||||||
"60",
|
|
||||||
"-X",
|
|
||||||
"PUT",
|
|
||||||
"--upload-file",
|
|
||||||
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
|
||||||
fmt.Sprintf(
|
|
||||||
"%s/v0/put/file_from_%s",
|
|
||||||
peerAPI,
|
|
||||||
hostname,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
fmt.Printf(
|
|
||||||
"Sending file from %s (%s) to %s (%s)\n",
|
|
||||||
hostname,
|
|
||||||
ips[hostname],
|
|
||||||
peername,
|
|
||||||
ip,
|
|
||||||
)
|
|
||||||
_, err = ExecuteCommand(
|
|
||||||
&tailscale,
|
|
||||||
command,
|
|
||||||
[]string{"ALL_PROXY=socks5://localhost:1055"},
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
attempts++
|
|
||||||
if attempts > 10 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.Nil(t, err)
|
|
||||||
}
|
}
|
||||||
|
retry(10, 1*time.Second, func() error {
|
||||||
|
fmt.Printf(
|
||||||
|
"Sending file from %s to %s\n",
|
||||||
|
hostname,
|
||||||
|
peername,
|
||||||
|
)
|
||||||
|
_, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
ExecuteCommandTimeout(60*time.Second),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,32 +669,70 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
for peername, ip := range ips {
|
for peername, ip := range ips {
|
||||||
|
if peername == hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||||
if peername != hostname {
|
command := []string{
|
||||||
command := []string{
|
"ls",
|
||||||
"ls",
|
fmt.Sprintf("/tmp/file_from_%s", peername),
|
||||||
fmt.Sprintf("/tmp/file_from_%s", peername),
|
|
||||||
}
|
|
||||||
fmt.Printf(
|
|
||||||
"Checking file in %s (%s) from %s (%s)\n",
|
|
||||||
hostname,
|
|
||||||
ips[hostname],
|
|
||||||
peername,
|
|
||||||
ip,
|
|
||||||
)
|
|
||||||
result, err := ExecuteCommand(
|
|
||||||
&tailscale,
|
|
||||||
command,
|
|
||||||
[]string{},
|
|
||||||
)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
fmt.Printf("Result for %s: %s\n", peername, result)
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
result,
|
|
||||||
fmt.Sprintf("/tmp/file_from_%s\n", peername),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf(
|
||||||
|
"Checking file in %s (%s) from %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Printf("Result for %s: %s\n", peername, result)
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
fmt.Sprintf("/tmp/file_from_%s\n", peername),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IntegrationTestSuite) TestPingAllPeersByHostname() {
|
||||||
|
for namespace, scales := range s.namespaces {
|
||||||
|
ips, err := getIPs(scales.tailscales)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
for hostname, tailscale := range scales.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=20",
|
||||||
|
"--until-direct=true",
|
||||||
|
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"Pinging using hostname from %s to %s\n",
|
||||||
|
hostname,
|
||||||
|
peername,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
|
&tailscale,
|
||||||
|
command,
|
||||||
|
[]string{},
|
||||||
|
)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Printf("Result for %s: %s\n", hostname, result)
|
||||||
|
assert.Contains(t, result, "pong")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -721,32 +744,31 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
|||||||
ips, err := getIPs(scales.tailscales)
|
ips, err := getIPs(scales.tailscales)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
for peername, ip := range ips {
|
for peername, ips := range ips {
|
||||||
|
if peername == hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||||
if peername != hostname {
|
command := []string{
|
||||||
command := []string{
|
"tailscale", "ip",
|
||||||
"tailscale", "ping",
|
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
|
||||||
"--timeout=10s",
|
}
|
||||||
"--c=20",
|
|
||||||
"--until-direct=true",
|
|
||||||
fmt.Sprintf("%s.%s.headscale.net", peername, namespace),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"Pinging using Hostname (magicdns) from %s (%s) to %s (%s)\n",
|
"Resolving name %s from %s\n",
|
||||||
hostname,
|
peername,
|
||||||
ips[hostname],
|
hostname,
|
||||||
peername,
|
)
|
||||||
ip,
|
result, err := ExecuteCommand(
|
||||||
)
|
&tailscale,
|
||||||
result, err := ExecuteCommand(
|
command,
|
||||||
&tailscale,
|
[]string{},
|
||||||
command,
|
)
|
||||||
[]string{},
|
assert.Nil(t, err)
|
||||||
)
|
fmt.Printf("Result for %s: %s\n", hostname, result)
|
||||||
assert.Nil(t, err)
|
|
||||||
fmt.Printf("Result for %s: %s\n", hostname, result)
|
for _, ip := range ips {
|
||||||
assert.Contains(t, result, "pong")
|
assert.Contains(t, result, ip.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -754,8 +776,10 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, error) {
|
func getIPs(
|
||||||
ips := make(map[string]netaddr.IP)
|
tailscales map[string]dockertest.Resource,
|
||||||
|
) (map[string][]netaddr.IP, error) {
|
||||||
|
ips := make(map[string][]netaddr.IP)
|
||||||
for hostname, tailscale := range tailscales {
|
for hostname, tailscale := range tailscales {
|
||||||
command := []string{"tailscale", "ip"}
|
command := []string{"tailscale", "ip"}
|
||||||
|
|
||||||
@@ -768,12 +792,17 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := netaddr.ParseIP(strings.TrimSuffix(result, "\n"))
|
for _, address := range strings.Split(result, "\n") {
|
||||||
if err != nil {
|
address = strings.TrimSuffix(address, "\n")
|
||||||
return nil, err
|
if len(address) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip, err := netaddr.ParseIP(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips[hostname] = append(ips[hostname], ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
ips[hostname] = ip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ips, nil
|
return ips, nil
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ log_level: trace
|
|||||||
acl_policy_path: ""
|
acl_policy_path: ""
|
||||||
db_type: sqlite3
|
db_type: sqlite3
|
||||||
ephemeral_node_inactivity_timeout: 30m
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
ip_prefixes:
|
||||||
|
- fd7a:115c:a1e0::/48
|
||||||
|
- 100.64.0.0/10
|
||||||
dns_config:
|
dns_config:
|
||||||
base_domain: headscale.net
|
base_domain: headscale.net
|
||||||
magic_dns: true
|
magic_dns: true
|
||||||
|
|||||||
305
machine.go
305
machine.go
@@ -1,6 +1,7 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -23,6 +24,7 @@ const (
|
|||||||
errMachineNotFound = Error("machine not found")
|
errMachineNotFound = Error("machine not found")
|
||||||
errMachineAlreadyRegistered = Error("machine already registered")
|
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")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Machine is a Headscale client.
|
// Machine is a Headscale client.
|
||||||
@@ -31,7 +33,7 @@ type Machine struct {
|
|||||||
MachineKey string `gorm:"type:varchar(64);unique_index"`
|
MachineKey string `gorm:"type:varchar(64);unique_index"`
|
||||||
NodeKey string
|
NodeKey string
|
||||||
DiscoKey string
|
DiscoKey string
|
||||||
IPAddress string
|
IPAddresses MachineAddresses
|
||||||
Name string
|
Name string
|
||||||
NamespaceID uint
|
NamespaceID uint
|
||||||
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
Namespace Namespace `gorm:"foreignKey:NamespaceID"`
|
||||||
@@ -64,6 +66,47 @@ func (machine Machine) isRegistered() bool {
|
|||||||
return machine.Registered
|
return machine.Registered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MachineAddresses []netaddr.IP
|
||||||
|
|
||||||
|
func (ma MachineAddresses) ToStringSlice() []string {
|
||||||
|
strSlice := make([]string, 0, len(ma))
|
||||||
|
for _, addr := range ma {
|
||||||
|
strSlice = append(strSlice, addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return strSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ma *MachineAddresses) Scan(destination interface{}) error {
|
||||||
|
switch value := destination.(type) {
|
||||||
|
case string:
|
||||||
|
addresses := strings.Split(value, ",")
|
||||||
|
*ma = (*ma)[:0]
|
||||||
|
for _, addr := range addresses {
|
||||||
|
if len(addr) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsed, err := netaddr.ParseIP(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*ma = append(*ma, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value return json value, implement driver.Valuer interface.
|
||||||
|
func (ma MachineAddresses) Value() (driver.Value, error) {
|
||||||
|
addresses := strings.Join(ma.ToStringSlice(), ",")
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
// isExpired returns whether the machine registration has expired.
|
// isExpired returns whether the machine registration has expired.
|
||||||
func (machine Machine) isExpired() bool {
|
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
|
||||||
@@ -76,6 +119,118 @@ func (machine Machine) isExpired() bool {
|
|||||||
return time.Now().UTC().After(*machine.Expiry)
|
return time.Now().UTC().After(*machine.Expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) ListAllMachines() ([]Machine, error) {
|
||||||
|
machines := []Machine{}
|
||||||
|
if err := h.db.Preload("AuthKey").
|
||||||
|
Preload("AuthKey.Namespace").
|
||||||
|
Preload("Namespace").
|
||||||
|
Where("registered").
|
||||||
|
Find(&machines).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return machines, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsAddresses(inputs []string, addrs []string) bool {
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if containsString(inputs, addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchSourceAndDestinationWithRule.
|
||||||
|
func matchSourceAndDestinationWithRule(
|
||||||
|
ruleSources []string,
|
||||||
|
ruleDestinations []string,
|
||||||
|
source []string,
|
||||||
|
destination []string,
|
||||||
|
) bool {
|
||||||
|
return containsAddresses(ruleSources, source) &&
|
||||||
|
containsAddresses(ruleDestinations, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFilteredByACLPeerss should return the list of peers authorized to be accessed from machine.
|
||||||
|
func getFilteredByACLPeers(
|
||||||
|
machines []Machine,
|
||||||
|
rules []tailcfg.FilterRule,
|
||||||
|
machine *Machine,
|
||||||
|
) Machines {
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("Finding peers filtered by ACLs")
|
||||||
|
|
||||||
|
peers := make(map[uint64]Machine)
|
||||||
|
// Aclfilter peers here. We are itering through machines in all namespaces and search through the computed aclRules
|
||||||
|
// for match between rule SrcIPs and DstPorts. If the rule is a match we allow the machine to be viewable.
|
||||||
|
|
||||||
|
// FIXME: On official control plane if a rule allow user A to talk to user B but NO rule allows user B to talk to
|
||||||
|
// user A. The behaviour is the following
|
||||||
|
//
|
||||||
|
// On official tailscale control plane:
|
||||||
|
// on first `tailscale status`` on node A we can see node B. The `tailscale status` command on node B doesn't show node A
|
||||||
|
// We can successfully establish a communication from A to B. When it's done, if we run the `tailscale status` command
|
||||||
|
// on node B again we can now see node A. It's not possible to establish a communication from node B to node A.
|
||||||
|
// On this implementation of the feature
|
||||||
|
// on any `tailscale status` command on node A we can see node B. The `tailscale status` command on node B DOES show A.
|
||||||
|
//
|
||||||
|
// I couldn't find a way to not clutter the output of `tailscale status` with all nodes that we could be talking to.
|
||||||
|
// In order to do this we would need to be able to identify that node A want to talk to node B but that Node B doesn't know
|
||||||
|
// how to talk to node A and then add the peering resource.
|
||||||
|
|
||||||
|
for _, peer := range machines {
|
||||||
|
if peer.ID == machine.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, rule := range rules {
|
||||||
|
var dst []string
|
||||||
|
for _, d := range rule.DstPorts {
|
||||||
|
dst = append(dst, d.IP)
|
||||||
|
}
|
||||||
|
if matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
machine.IPAddresses.ToStringSlice(),
|
||||||
|
peer.IPAddresses.ToStringSlice(),
|
||||||
|
) || // match source and destination
|
||||||
|
matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
machine.IPAddresses.ToStringSlice(),
|
||||||
|
[]string{"*"},
|
||||||
|
) || // match source and all destination
|
||||||
|
matchSourceAndDestinationWithRule(
|
||||||
|
rule.SrcIPs,
|
||||||
|
dst,
|
||||||
|
peer.IPAddresses.ToStringSlice(),
|
||||||
|
machine.IPAddresses.ToStringSlice(),
|
||||||
|
) { // match return path
|
||||||
|
peers[peer.ID] = peer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizedPeers := make([]Machine, 0, len(peers))
|
||||||
|
for _, m := range peers {
|
||||||
|
authorizedPeers = append(authorizedPeers, m)
|
||||||
|
}
|
||||||
|
sort.Slice(
|
||||||
|
authorizedPeers,
|
||||||
|
func(i, j int) bool { return authorizedPeers[i].ID < authorizedPeers[j].ID },
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msgf("Found some machines: %v", machines)
|
||||||
|
|
||||||
|
return authorizedPeers
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Headscale) getDirectPeers(machine *Machine) (Machines, error) {
|
func (h *Headscale) getDirectPeers(machine *Machine) (Machines, error) {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
@@ -163,39 +318,54 @@ func (h *Headscale) getSharedTo(machine *Machine) (Machines, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
|
func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
|
||||||
direct, err := h.getDirectPeers(machine)
|
var peers Machines
|
||||||
if err != nil {
|
var err error
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot fetch peers")
|
|
||||||
|
|
||||||
return Machines{}, err
|
// If ACLs rules are defined, filter visible host list with the ACLs
|
||||||
|
// else use the classic namespace scope
|
||||||
|
if h.aclPolicy != nil {
|
||||||
|
var machines []Machine
|
||||||
|
machines, err = h.ListAllMachines()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error retrieving list of machines")
|
||||||
|
|
||||||
|
return Machines{}, err
|
||||||
|
}
|
||||||
|
peers = getFilteredByACLPeers(machines, h.aclRules, machine)
|
||||||
|
} else {
|
||||||
|
direct, err := h.getDirectPeers(machine)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot fetch peers")
|
||||||
|
|
||||||
|
return Machines{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
shared, err := h.getShared(machine)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot fetch peers")
|
||||||
|
|
||||||
|
return Machines{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedTo, err := h.getSharedTo(machine)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot fetch peers")
|
||||||
|
|
||||||
|
return Machines{}, err
|
||||||
|
}
|
||||||
|
peers = append(direct, shared...)
|
||||||
|
peers = append(peers, sharedTo...)
|
||||||
}
|
}
|
||||||
|
|
||||||
shared, err := h.getShared(machine)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot fetch peers")
|
|
||||||
|
|
||||||
return Machines{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedTo, err := h.getSharedTo(machine)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot fetch peers")
|
|
||||||
|
|
||||||
return Machines{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
peers := append(direct, shared...)
|
|
||||||
peers = append(peers, sharedTo...)
|
|
||||||
|
|
||||||
sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
|
sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
|
||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
@@ -310,13 +480,20 @@ func (h *Headscale) DeleteMachine(machine *Machine) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
machine.Registered = false
|
machine.Registered = false
|
||||||
namespaceID := machine.NamespaceID
|
|
||||||
h.db.Save(&machine) // we mark it as unregistered, just in case
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.RequestMapUpdates(namespaceID)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) TouchMachine(machine *Machine) error {
|
||||||
|
return h.db.Updates(Machine{
|
||||||
|
ID: machine.ID,
|
||||||
|
LastSeen: machine.LastSeen,
|
||||||
|
LastSuccessfulUpdate: machine.LastSuccessfulUpdate,
|
||||||
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// HardDeleteMachine hard deletes a Machine from the database.
|
// HardDeleteMachine hard deletes a Machine from the database.
|
||||||
@@ -326,12 +503,11 @@ func (h *Headscale) HardDeleteMachine(machine *Machine) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
namespaceID := machine.NamespaceID
|
|
||||||
if err := h.db.Unscoped().Delete(&machine).Error; err != nil {
|
if err := h.db.Unscoped().Delete(&machine).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.RequestMapUpdates(namespaceID)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHostInfo returns a Hostinfo struct for the machine.
|
// GetHostInfo returns a Hostinfo struct for the machine.
|
||||||
@@ -377,14 +553,18 @@ func (h *Headscale) isOutdated(machine *Machine) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastChange := h.getLastStateChange(namespaces...)
|
lastChange := h.getLastStateChange(namespaces...)
|
||||||
|
lastUpdate := machine.CreatedAt
|
||||||
|
if machine.LastSuccessfulUpdate != nil {
|
||||||
|
lastUpdate = *machine.LastSuccessfulUpdate
|
||||||
|
}
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Time("last_successful_update", *machine.LastSuccessfulUpdate).
|
Time("last_successful_update", lastChange).
|
||||||
Time("last_state_change", lastChange).
|
Time("last_state_change", lastUpdate).
|
||||||
Msgf("Checking if %s is missing updates", machine.Name)
|
Msgf("Checking if %s is missing updates", machine.Name)
|
||||||
|
|
||||||
return machine.LastSuccessfulUpdate.Before(lastChange)
|
return lastUpdate.Before(lastChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (machine Machine) String() string {
|
func (machine Machine) String() string {
|
||||||
@@ -470,22 +650,14 @@ func (machine Machine) toNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
addrs := []netaddr.IPPrefix{}
|
addrs := []netaddr.IPPrefix{}
|
||||||
ip, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/32", machine.IPAddress))
|
for _, machineAddress := range machine.IPAddresses {
|
||||||
if err != nil {
|
ip := netaddr.IPPrefixFrom(machineAddress, machineAddress.BitLen())
|
||||||
log.Trace().
|
addrs = append(addrs, ip)
|
||||||
Caller().
|
|
||||||
Str("ip", machine.IPAddress).
|
|
||||||
Msgf("Failed to parse IP Prefix from IP: %s", machine.IPAddress)
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
addrs = append(addrs, ip) // missing the ipv6 ?
|
|
||||||
|
|
||||||
allowedIPs := []netaddr.IPPrefix{}
|
allowedIPs := append(
|
||||||
allowedIPs = append(
|
[]netaddr.IPPrefix{},
|
||||||
allowedIPs,
|
addrs...) // we append the node own IP, as it is required by the clients
|
||||||
ip,
|
|
||||||
) // we append the node own IP, as it is required by the clients
|
|
||||||
|
|
||||||
if includeRoutes {
|
if includeRoutes {
|
||||||
routesStr := []string{}
|
routesStr := []string{}
|
||||||
@@ -552,7 +724,11 @@ func (machine Machine) toNode(
|
|||||||
hostname = fmt.Sprintf(
|
hostname = fmt.Sprintf(
|
||||||
"%s.%s.%s",
|
"%s.%s.%s",
|
||||||
machine.Name,
|
machine.Name,
|
||||||
machine.Namespace.Name,
|
strings.ReplaceAll(
|
||||||
|
machine.Namespace.Name,
|
||||||
|
"@",
|
||||||
|
".",
|
||||||
|
), // Replace @ with . for valid domain for machine
|
||||||
baseDomain,
|
baseDomain,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -592,11 +768,11 @@ func (machine *Machine) toProto() *v1.Machine {
|
|||||||
Id: machine.ID,
|
Id: machine.ID,
|
||||||
MachineKey: machine.MachineKey,
|
MachineKey: machine.MachineKey,
|
||||||
|
|
||||||
NodeKey: machine.NodeKey,
|
NodeKey: machine.NodeKey,
|
||||||
DiscoKey: machine.DiscoKey,
|
DiscoKey: machine.DiscoKey,
|
||||||
IpAddress: machine.IPAddress,
|
IpAddresses: machine.IPAddresses.ToStringSlice(),
|
||||||
Name: machine.Name,
|
Name: machine.Name,
|
||||||
Namespace: machine.Namespace.toProto(),
|
Namespace: machine.Namespace.toProto(),
|
||||||
|
|
||||||
Registered: machine.Registered,
|
Registered: machine.Registered,
|
||||||
|
|
||||||
@@ -695,7 +871,7 @@ func (h *Headscale) RegisterMachine(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := h.getAvailableIP()
|
ips, err := h.getAvailableIPs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
@@ -709,10 +885,10 @@ func (h *Headscale) RegisterMachine(
|
|||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", ip.String()).
|
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
|
||||||
Msg("Found IP for host")
|
Msg("Found IP for host")
|
||||||
|
|
||||||
machine.IPAddress = ip.String()
|
machine.IPAddresses = ips
|
||||||
machine.NamespaceID = namespace.ID
|
machine.NamespaceID = namespace.ID
|
||||||
machine.Registered = true
|
machine.Registered = true
|
||||||
machine.RegisterMethod = RegisterMethodCLI
|
machine.RegisterMethod = RegisterMethodCLI
|
||||||
@@ -722,7 +898,7 @@ func (h *Headscale) RegisterMachine(
|
|||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", ip.String()).
|
Str("ip", strings.Join(ips.ToStringSlice(), ",")).
|
||||||
Msg("Machine registered with the database")
|
Msg("Machine registered with the database")
|
||||||
|
|
||||||
return machine, nil
|
return machine, nil
|
||||||
@@ -817,11 +993,6 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
|
|||||||
machine.EnabledRoutes = datatypes.JSON(routes)
|
machine.EnabledRoutes = datatypes.JSON(routes)
|
||||||
h.db.Save(&machine)
|
h.db.Save(&machine)
|
||||||
|
|
||||||
err = h.RequestMapUpdates(machine.NamespaceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
297
machine_test.go
297
machine_test.go
@@ -1,11 +1,15 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestGetMachine(c *check.C) {
|
func (s *Suite) TestGetMachine(c *check.C) {
|
||||||
@@ -87,18 +91,6 @@ func (s *Suite) TestDeleteMachine(c *check.C) {
|
|||||||
err = app.DeleteMachine(&machine)
|
err = app.DeleteMachine(&machine)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
namespacesPendingUpdates, err := app.getValue("namespaces_pending_updates")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
names := []string{}
|
|
||||||
err = json.Unmarshal([]byte(namespacesPendingUpdates), &names)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(names, check.DeepEquals, []string{namespace.Name})
|
|
||||||
|
|
||||||
app.checkForNamespacesPendingUpdates()
|
|
||||||
|
|
||||||
namespacesPendingUpdates, _ = app.getValue("namespaces_pending_updates")
|
|
||||||
c.Assert(namespacesPendingUpdates, check.Equals, "")
|
|
||||||
_, err = app.GetMachine(namespace.Name, "testmachine")
|
_, err = app.GetMachine(namespace.Name, "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
}
|
}
|
||||||
@@ -166,6 +158,89 @@ func (s *Suite) TestGetDirectPeers(c *check.C) {
|
|||||||
c.Assert(peersOfMachine0[8].Name, check.Equals, "testmachine10")
|
c.Assert(peersOfMachine0[8].Name, check.Equals, "testmachine10")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||||
|
type base struct {
|
||||||
|
namespace *Namespace
|
||||||
|
key *PreAuthKey
|
||||||
|
}
|
||||||
|
|
||||||
|
stor := make([]base, 0)
|
||||||
|
|
||||||
|
for _, name := range []string{"test", "admin"} {
|
||||||
|
namespace, err := app.CreateNamespace(name)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
stor = append(stor, base{namespace, pak})
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := app.GetMachineByID(0)
|
||||||
|
c.Assert(err, check.NotNil)
|
||||||
|
|
||||||
|
for index := 0; index <= 10; index++ {
|
||||||
|
machine := Machine{
|
||||||
|
ID: uint64(index),
|
||||||
|
MachineKey: "foo" + strconv.Itoa(index),
|
||||||
|
NodeKey: "bar" + strconv.Itoa(index),
|
||||||
|
DiscoKey: "faa" + strconv.Itoa(index),
|
||||||
|
IPAddresses: MachineAddresses{
|
||||||
|
netaddr.MustParseIP(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
|
||||||
|
},
|
||||||
|
Name: "testmachine" + strconv.Itoa(index),
|
||||||
|
NamespaceID: stor[index%2].namespace.ID,
|
||||||
|
Registered: true,
|
||||||
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
|
AuthKeyID: uint(stor[index%2].key.ID),
|
||||||
|
}
|
||||||
|
app.db.Save(&machine)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.aclPolicy = &ACLPolicy{
|
||||||
|
Groups: map[string][]string{
|
||||||
|
"group:test": {"admin"},
|
||||||
|
},
|
||||||
|
Hosts: map[string]netaddr.IPPrefix{},
|
||||||
|
TagOwners: map[string][]string{},
|
||||||
|
ACLs: []ACL{
|
||||||
|
{Action: "accept", Users: []string{"admin"}, Ports: []string{"*:*"}},
|
||||||
|
{Action: "accept", Users: []string{"test"}, Ports: []string{"test:*"}},
|
||||||
|
},
|
||||||
|
Tests: []ACLTest{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.UpdateACLRules()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
adminMachine, err := app.GetMachineByID(1)
|
||||||
|
c.Logf("Machine(%v), namespace: %v", adminMachine.Name, adminMachine.Namespace)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
testMachine, err := app.GetMachineByID(2)
|
||||||
|
c.Logf("Machine(%v), namespace: %v", testMachine.Name, testMachine.Namespace)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
_, err = testMachine.GetHostInfo()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
machines, err := app.ListAllMachines()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
peersOfTestMachine := getFilteredByACLPeers(machines, app.aclRules, testMachine)
|
||||||
|
peersOfAdminMachine := getFilteredByACLPeers(machines, app.aclRules, adminMachine)
|
||||||
|
|
||||||
|
c.Log(peersOfTestMachine)
|
||||||
|
c.Assert(len(peersOfTestMachine), check.Equals, 4)
|
||||||
|
c.Assert(peersOfTestMachine[0].Name, check.Equals, "testmachine4")
|
||||||
|
c.Assert(peersOfTestMachine[1].Name, check.Equals, "testmachine6")
|
||||||
|
c.Assert(peersOfTestMachine[3].Name, check.Equals, "testmachine10")
|
||||||
|
|
||||||
|
c.Log(peersOfAdminMachine)
|
||||||
|
c.Assert(len(peersOfAdminMachine), check.Equals, 9)
|
||||||
|
c.Assert(peersOfAdminMachine[0].Name, check.Equals, "testmachine2")
|
||||||
|
c.Assert(peersOfAdminMachine[2].Name, check.Equals, "testmachine4")
|
||||||
|
c.Assert(peersOfAdminMachine[5].Name, check.Equals, "testmachine7")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Suite) TestExpireMachine(c *check.C) {
|
func (s *Suite) TestExpireMachine(c *check.C) {
|
||||||
namespace, err := app.CreateNamespace("test")
|
namespace, err := app.CreateNamespace("test")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -199,3 +274,199 @@ func (s *Suite) TestExpireMachine(c *check.C) {
|
|||||||
|
|
||||||
c.Assert(machineFromDB.isExpired(), check.Equals, true)
|
c.Assert(machineFromDB.isExpired(), check.Equals, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
||||||
|
input := MachineAddresses([]netaddr.IP{
|
||||||
|
netaddr.MustParseIP("192.0.2.1"),
|
||||||
|
netaddr.MustParseIP("2001:db8::1"),
|
||||||
|
})
|
||||||
|
serialized, err := input.Value()
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
if serial, ok := serialized.(string); ok {
|
||||||
|
c.Assert(serial, check.Equals, "192.0.2.1,2001:db8::1")
|
||||||
|
}
|
||||||
|
|
||||||
|
var deserialized MachineAddresses
|
||||||
|
err = deserialized.Scan(serialized)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
c.Assert(len(deserialized), check.Equals, len(input))
|
||||||
|
for i := range deserialized {
|
||||||
|
c.Assert(deserialized[i], check.Equals, input[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getFilteredByACLPeers(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
machines []Machine
|
||||||
|
rules []tailcfg.FilterRule
|
||||||
|
machine *Machine
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Machines
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all hosts can talk to each other",
|
||||||
|
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{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
||||||
|
DstPorts: []tailcfg.NetPortRange{
|
||||||
|
{IP: "*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
|
||||||
|
Namespace: Namespace{Name: "mickael"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "One host can talk to another, but not 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{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
|
||||||
|
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: "host cannot directly talk to destination, but return path is authorized",
|
||||||
|
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{"100.64.0.3"},
|
||||||
|
DstPorts: []tailcfg.NetPortRange{
|
||||||
|
{IP: "100.64.0.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
machine: &Machine{ // current machine
|
||||||
|
ID: 1,
|
||||||
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||||
|
Namespace: Namespace{Name: "marc"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: Machines{
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
|
||||||
|
Namespace: Namespace{Name: "mickael"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := getFilteredByACLPeers(
|
||||||
|
tt.args.machines,
|
||||||
|
tt.args.rules,
|
||||||
|
tt.args.machine,
|
||||||
|
)
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("getFilteredByACLPeers() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -104,11 +102,6 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error {
|
|||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.RequestMapUpdates(oldNamespace.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,92 +180,6 @@ func (h *Headscale) SetMachineNamespace(machine *Machine, namespaceName string)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kradalby): Remove the need for this.
|
|
||||||
// RequestMapUpdates signals the KV worker to update the maps for this namespace.
|
|
||||||
func (h *Headscale) RequestMapUpdates(namespaceID uint) error {
|
|
||||||
namespace := Namespace{}
|
|
||||||
if err := h.db.First(&namespace, namespaceID).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates")
|
|
||||||
if err != nil || namespacesPendingUpdates == "" {
|
|
||||||
err = h.setValue(
|
|
||||||
"namespaces_pending_updates",
|
|
||||||
fmt.Sprintf(`["%s"]`, namespace.Name),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
names := []string{}
|
|
||||||
err = json.Unmarshal([]byte(namespacesPendingUpdates), &names)
|
|
||||||
if err != nil {
|
|
||||||
err = h.setValue(
|
|
||||||
"namespaces_pending_updates",
|
|
||||||
fmt.Sprintf(`["%s"]`, namespace.Name),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
names = append(names, namespace.Name)
|
|
||||||
data, err := json.Marshal(names)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("func", "RequestMapUpdates").
|
|
||||||
Err(err).
|
|
||||||
Msg("Could not marshal namespaces_pending_updates")
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.setValue("namespaces_pending_updates", string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Headscale) checkForNamespacesPendingUpdates() {
|
|
||||||
namespacesPendingUpdates, err := h.getValue("namespaces_pending_updates")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if namespacesPendingUpdates == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
namespaces := []string{}
|
|
||||||
err = json.Unmarshal([]byte(namespacesPendingUpdates), &namespaces)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, namespace := range namespaces {
|
|
||||||
log.Trace().
|
|
||||||
Str("func", "RequestMapUpdates").
|
|
||||||
Str("machine", namespace).
|
|
||||||
Msg("Sending updates to nodes in namespacespace")
|
|
||||||
h.setLastStateChangeToNow(namespace)
|
|
||||||
}
|
|
||||||
newPendingUpdateValue, err := h.getValue("namespaces_pending_updates")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if namespacesPendingUpdates == newPendingUpdateValue { // only clear when no changes, so we notified everybody
|
|
||||||
err = h.setValue("namespaces_pending_updates", "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("func", "checkForNamespacesPendingUpdates").
|
|
||||||
Err(err).
|
|
||||||
Msg("Could not save to KV")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespace) toUser() *tailcfg.User {
|
func (n *Namespace) toUser() *tailcfg.User {
|
||||||
user := tailcfg.User{
|
user := tailcfg.User{
|
||||||
ID: tailcfg.UserID(n.ID),
|
ID: tailcfg.UserID(n.ID),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
|
func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
|
||||||
@@ -146,7 +147,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.1",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(preAuthKeyShared1.ID),
|
AuthKeyID: uint(preAuthKeyShared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared1)
|
app.db.Save(machineInShared1)
|
||||||
@@ -164,7 +165,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||||||
Namespace: *namespaceShared2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.2",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||||
AuthKeyID: uint(preAuthKeyShared2.ID),
|
AuthKeyID: uint(preAuthKeyShared2.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared2)
|
app.db.Save(machineInShared2)
|
||||||
@@ -182,7 +183,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||||||
Namespace: *namespaceShared3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.3",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||||
AuthKeyID: uint(preAuthKeyShared3.ID),
|
AuthKeyID: uint(preAuthKeyShared3.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machineInShared3)
|
app.db.Save(machineInShared3)
|
||||||
@@ -200,7 +201,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
|||||||
Namespace: *namespaceShared1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine2InShared1)
|
app.db.Save(machine2InShared1)
|
||||||
|
|||||||
5
oidc.go
5
oidc.go
@@ -126,6 +126,7 @@ 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
|
||||||
@@ -316,7 +317,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := h.getAvailableIP()
|
ips, err := h.getAvailableIPs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
@@ -330,7 +331,7 @@ func (h *Headscale) OIDCCallback(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
machine.IPAddress = ip.String()
|
machine.IPAddresses = ips
|
||||||
machine.NamespaceID = namespace.ID
|
machine.NamespaceID = namespace.ID
|
||||||
machine.Registered = true
|
machine.Registered = true
|
||||||
machine.RegisterMethod = RegisterMethodOIDC
|
machine.RegisterMethod = RegisterMethodOIDC
|
||||||
|
|||||||
231
poll.go
231
poll.go
@@ -1,8 +1,10 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@@ -74,6 +76,8 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String())
|
Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String())
|
||||||
ctx.String(http.StatusInternalServerError, "")
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
@@ -81,12 +85,26 @@ 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, _ := json.Marshal(req.Hostinfo)
|
hostinfo, err := json.Marshal(req.Hostinfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
machine.Name = req.Hostinfo.Hostname
|
machine.Name = req.Hostinfo.Hostname
|
||||||
machine.HostInfo = datatypes.JSON(hostinfo)
|
machine.HostInfo = datatypes.JSON(hostinfo)
|
||||||
machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
|
machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
// update ACLRules with peer informations (to update server tags if necessary)
|
||||||
|
if h.aclPolicy != nil {
|
||||||
|
err = h.UpdateACLRules()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("func", "handleAuthKey").
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
// From Tailscale client:
|
// From Tailscale client:
|
||||||
//
|
//
|
||||||
// ReadOnly is whether the client just wants to fetch the MapResponse,
|
// ReadOnly is whether the client just wants to fetch the MapResponse,
|
||||||
@@ -96,11 +114,21 @@ 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, _ := json.Marshal(req.Endpoints)
|
endpoints, err := json.Marshal(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.Endpoints = datatypes.JSON(endpoints)
|
||||||
machine.LastSeen = &now
|
machine.LastSeen = &now
|
||||||
}
|
}
|
||||||
h.db.Save(&machine)
|
h.db.Updates(machine)
|
||||||
|
|
||||||
data, err := h.getMapResponse(machineKey, req, machine)
|
data, err := h.getMapResponse(machineKey, req, machine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -152,14 +180,33 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
Str("id", ctx.Param("id")).
|
Str("id", ctx.Param("id")).
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Loading or creating update channel")
|
Msg("Loading or creating update channel")
|
||||||
updateChan := make(chan struct{})
|
|
||||||
|
|
||||||
pollDataChan := make(chan []byte)
|
// TODO: could probably remove all that duplication once generics land.
|
||||||
|
closeChanWithLog := func(channel interface{}, name string) {
|
||||||
|
log.Trace().
|
||||||
|
Str("handler", "PollNetMap").
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Str("channel", "Done").
|
||||||
|
Msg(fmt.Sprintf("Closing %s channel", name))
|
||||||
|
|
||||||
|
switch c := channel.(type) {
|
||||||
|
case (chan struct{}):
|
||||||
|
close(c)
|
||||||
|
|
||||||
|
case (chan []byte):
|
||||||
|
close(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const chanSize = 8
|
||||||
|
updateChan := make(chan struct{}, chanSize)
|
||||||
|
defer closeChanWithLog(updateChan, "updateChan")
|
||||||
|
|
||||||
|
pollDataChan := make(chan []byte, chanSize)
|
||||||
|
defer closeChanWithLog(pollDataChan, "pollDataChan")
|
||||||
|
|
||||||
keepAliveChan := make(chan []byte)
|
keepAliveChan := make(chan []byte)
|
||||||
|
defer closeChanWithLog(keepAliveChan, "keepAliveChan")
|
||||||
cancelKeepAlive := make(chan struct{})
|
|
||||||
defer close(cancelKeepAlive)
|
|
||||||
|
|
||||||
if req.OmitPeers && !req.Stream {
|
if req.OmitPeers && !req.Stream {
|
||||||
log.Info().
|
log.Info().
|
||||||
@@ -170,9 +217,9 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
|
|
||||||
// It sounds like we should update the nodes when we have received a endpoint update
|
// It sounds like we should update the nodes when we have received a endpoint update
|
||||||
// even tho the comments in the tailscale code dont explicitly say so.
|
// even tho the comments in the tailscale code dont explicitly say so.
|
||||||
updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update").
|
updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "endpoint-update").
|
||||||
Inc()
|
Inc()
|
||||||
go func() { updateChan <- struct{}{} }()
|
updateChan <- struct{}{}
|
||||||
|
|
||||||
return
|
return
|
||||||
} else if req.OmitPeers && req.Stream {
|
} else if req.OmitPeers && req.Stream {
|
||||||
@@ -193,15 +240,15 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Sending initial map")
|
Msg("Sending initial map")
|
||||||
go func() { pollDataChan <- data }()
|
pollDataChan <- data
|
||||||
|
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Notifying peers")
|
Msg("Notifying peers")
|
||||||
updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update").
|
updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "full-update").
|
||||||
Inc()
|
Inc()
|
||||||
go func() { updateChan <- struct{}{} }()
|
updateChan <- struct{}{}
|
||||||
|
|
||||||
h.PollNetMapStream(
|
h.PollNetMapStream(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -211,7 +258,6 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
|
|||||||
pollDataChan,
|
pollDataChan,
|
||||||
keepAliveChan,
|
keepAliveChan,
|
||||||
updateChan,
|
updateChan,
|
||||||
cancelKeepAlive,
|
|
||||||
)
|
)
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("handler", "PollNetMap").
|
Str("handler", "PollNetMap").
|
||||||
@@ -231,16 +277,20 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
pollDataChan chan []byte,
|
pollDataChan chan []byte,
|
||||||
keepAliveChan chan []byte,
|
keepAliveChan chan []byte,
|
||||||
updateChan chan struct{},
|
updateChan chan struct{},
|
||||||
cancelKeepAlive chan struct{},
|
|
||||||
) {
|
) {
|
||||||
go h.scheduledPollWorker(
|
{
|
||||||
cancelKeepAlive,
|
ctx, cancel := context.WithCancel(ctx.Request.Context())
|
||||||
updateChan,
|
defer cancel()
|
||||||
keepAliveChan,
|
|
||||||
machineKey,
|
go h.scheduledPollWorker(
|
||||||
mapRequest,
|
ctx,
|
||||||
machine,
|
updateChan,
|
||||||
)
|
keepAliveChan,
|
||||||
|
machineKey,
|
||||||
|
mapRequest,
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Stream(func(writer io.Writer) bool {
|
ctx.Stream(func(writer io.Writer) bool {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
@@ -289,6 +339,10 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("channel", "pollData").
|
Str("channel", "pollData").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot update machine from database")
|
Msg("Cannot update machine from database")
|
||||||
|
|
||||||
|
// client has been removed from database
|
||||||
|
// since the stream opened, terminate connection.
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
machine.LastSeen = &now
|
machine.LastSeen = &now
|
||||||
@@ -297,13 +351,22 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Set(float64(now.Unix()))
|
Set(float64(now.Unix()))
|
||||||
machine.LastSuccessfulUpdate = &now
|
machine.LastSuccessfulUpdate = &now
|
||||||
|
|
||||||
h.db.Save(&machine)
|
err = h.TouchMachine(machine)
|
||||||
log.Trace().
|
if err != nil {
|
||||||
Str("handler", "PollNetMapStream").
|
log.Error().
|
||||||
Str("machine", machine.Name).
|
Str("handler", "PollNetMapStream").
|
||||||
Str("channel", "pollData").
|
Str("machine", machine.Name).
|
||||||
Int("bytes", len(data)).
|
Str("channel", "pollData").
|
||||||
Msg("Machine entry in database updated successfully after sending pollData")
|
Err(err).
|
||||||
|
Msg("Cannot update machine LastSuccessfulUpdate")
|
||||||
|
} else {
|
||||||
|
log.Trace().
|
||||||
|
Str("handler", "PollNetMapStream").
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Str("channel", "pollData").
|
||||||
|
Int("bytes", len(data)).
|
||||||
|
Msg("Machine entry in database updated successfully after sending pollData")
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -342,16 +405,29 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("channel", "keepAlive").
|
Str("channel", "keepAlive").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot update machine from database")
|
Msg("Cannot update machine from database")
|
||||||
|
|
||||||
|
// client has been removed from database
|
||||||
|
// since the stream opened, terminate connection.
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
machine.LastSeen = &now
|
machine.LastSeen = &now
|
||||||
h.db.Save(&machine)
|
err = h.TouchMachine(machine)
|
||||||
log.Trace().
|
if err != nil {
|
||||||
Str("handler", "PollNetMapStream").
|
log.Error().
|
||||||
Str("machine", machine.Name).
|
Str("handler", "PollNetMapStream").
|
||||||
Str("channel", "keepAlive").
|
Str("machine", machine.Name).
|
||||||
Int("bytes", len(data)).
|
Str("channel", "keepAlive").
|
||||||
Msg("Machine updated successfully after sending keep alive")
|
Err(err).
|
||||||
|
Msg("Cannot update machine LastSeen")
|
||||||
|
} else {
|
||||||
|
log.Trace().
|
||||||
|
Str("handler", "PollNetMapStream").
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Str("channel", "keepAlive").
|
||||||
|
Int("bytes", len(data)).
|
||||||
|
Msg("Machine updated successfully after sending keep alive")
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -361,13 +437,17 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Msg("Received a request for update")
|
Msg("Received a request for update")
|
||||||
updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name).
|
updateRequestsReceivedOnChannel.WithLabelValues(machine.Namespace.Name, machine.Name).
|
||||||
Inc()
|
Inc()
|
||||||
if h.isOutdated(machine) {
|
if h.isOutdated(machine) {
|
||||||
|
var lastUpdate time.Time
|
||||||
|
if machine.LastSuccessfulUpdate != nil {
|
||||||
|
lastUpdate = *machine.LastSuccessfulUpdate
|
||||||
|
}
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("handler", "PollNetMapStream").
|
Str("handler", "PollNetMapStream").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Time("last_successful_update", *machine.LastSuccessfulUpdate).
|
Time("last_successful_update", lastUpdate).
|
||||||
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
||||||
Msgf("There has been updates since the last successful update to %s", machine.Name)
|
Msgf("There has been updates since the last successful update to %s", machine.Name)
|
||||||
data, err := h.getMapResponse(machineKey, mapRequest, machine)
|
data, err := h.getMapResponse(machineKey, mapRequest, machine)
|
||||||
@@ -387,7 +467,7 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not write the map response")
|
Msg("Could not write the map response")
|
||||||
updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "failed").
|
updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Name, "failed").
|
||||||
Inc()
|
Inc()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -397,7 +477,7 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Msg("Updated Map has been sent")
|
Msg("Updated Map has been sent")
|
||||||
updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "success").
|
updateRequestsSentToNode.WithLabelValues(machine.Namespace.Name, machine.Name, "success").
|
||||||
Inc()
|
Inc()
|
||||||
|
|
||||||
// Keep track of the last successful update,
|
// Keep track of the last successful update,
|
||||||
@@ -415,6 +495,10 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("channel", "update").
|
Str("channel", "update").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot update machine from database")
|
Msg("Cannot update machine from database")
|
||||||
|
|
||||||
|
// client has been removed from database
|
||||||
|
// since the stream opened, terminate connection.
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
@@ -422,12 +506,24 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Set(float64(now.Unix()))
|
Set(float64(now.Unix()))
|
||||||
machine.LastSuccessfulUpdate = &now
|
machine.LastSuccessfulUpdate = &now
|
||||||
|
|
||||||
h.db.Save(&machine)
|
err = h.TouchMachine(machine)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("handler", "PollNetMapStream").
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Str("channel", "update").
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot update machine LastSuccessfulUpdate")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
var lastUpdate time.Time
|
||||||
|
if machine.LastSuccessfulUpdate != nil {
|
||||||
|
lastUpdate = *machine.LastSuccessfulUpdate
|
||||||
|
}
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("handler", "PollNetMapStream").
|
Str("handler", "PollNetMapStream").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Time("last_successful_update", *machine.LastSuccessfulUpdate).
|
Time("last_successful_update", lastUpdate).
|
||||||
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
||||||
Msgf("%s is up to date", machine.Name)
|
Msgf("%s is up to date", machine.Name)
|
||||||
}
|
}
|
||||||
@@ -450,39 +546,22 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
Str("channel", "Done").
|
Str("channel", "Done").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot update machine from database")
|
Msg("Cannot update machine from database")
|
||||||
|
|
||||||
|
// client has been removed from database
|
||||||
|
// since the stream opened, terminate connection.
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
machine.LastSeen = &now
|
machine.LastSeen = &now
|
||||||
h.db.Save(&machine)
|
err = h.TouchMachine(machine)
|
||||||
|
if err != nil {
|
||||||
log.Trace().
|
log.Error().
|
||||||
Str("handler", "PollNetMapStream").
|
Str("handler", "PollNetMapStream").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Str("channel", "Done").
|
Str("channel", "Done").
|
||||||
Msg("Cancelling keepAlive channel")
|
Err(err).
|
||||||
cancelKeepAlive <- struct{}{}
|
Msg("Cannot update machine LastSeen")
|
||||||
|
}
|
||||||
log.Trace().
|
|
||||||
Str("handler", "PollNetMapStream").
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Str("channel", "Done").
|
|
||||||
Msg("Closing update channel")
|
|
||||||
// h.closeUpdateChannel(m)
|
|
||||||
close(updateChan)
|
|
||||||
|
|
||||||
log.Trace().
|
|
||||||
Str("handler", "PollNetMapStream").
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Str("channel", "Done").
|
|
||||||
Msg("Closing pollData channel")
|
|
||||||
close(pollDataChan)
|
|
||||||
|
|
||||||
log.Trace().
|
|
||||||
Str("handler", "PollNetMapStream").
|
|
||||||
Str("machine", machine.Name).
|
|
||||||
Str("channel", "Done").
|
|
||||||
Msg("Closing keepAliveChan channel")
|
|
||||||
close(keepAliveChan)
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -490,7 +569,7 @@ func (h *Headscale) PollNetMapStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) scheduledPollWorker(
|
func (h *Headscale) scheduledPollWorker(
|
||||||
cancelChan <-chan struct{},
|
ctx context.Context,
|
||||||
updateChan chan<- struct{},
|
updateChan chan<- struct{},
|
||||||
keepAliveChan chan<- []byte,
|
keepAliveChan chan<- []byte,
|
||||||
machineKey key.MachinePublic,
|
machineKey key.MachinePublic,
|
||||||
@@ -502,7 +581,7 @@ func (h *Headscale) scheduledPollWorker(
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-cancelChan:
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-keepAliveTicker.C:
|
case <-keepAliveTicker.C:
|
||||||
@@ -527,7 +606,7 @@ func (h *Headscale) scheduledPollWorker(
|
|||||||
Str("func", "scheduledPollWorker").
|
Str("func", "scheduledPollWorker").
|
||||||
Str("machine", machine.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Sending update request")
|
Msg("Sending update request")
|
||||||
updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "scheduled-update").
|
updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Name, "scheduled-update").
|
||||||
Inc()
|
Inc()
|
||||||
updateChan <- struct{}{}
|
updateChan <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|||||||
35
proto/headscale/v1/apikey.proto
Normal file
35
proto/headscale/v1/apikey.proto
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package headscale.v1;
|
||||||
|
option go_package = "github.com/juanfont/headscale/gen/go/v1";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
message ApiKey {
|
||||||
|
uint64 id = 1;
|
||||||
|
string prefix = 2;
|
||||||
|
google.protobuf.Timestamp expiration = 3;
|
||||||
|
google.protobuf.Timestamp created_at = 4;
|
||||||
|
google.protobuf.Timestamp last_seen = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateApiKeyRequest {
|
||||||
|
google.protobuf.Timestamp expiration = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateApiKeyResponse {
|
||||||
|
string api_key = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExpireApiKeyRequest {
|
||||||
|
string prefix = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ExpireApiKeyResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListApiKeysRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListApiKeysResponse {
|
||||||
|
repeated ApiKey api_keys = 1;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import "headscale/v1/namespace.proto";
|
|||||||
import "headscale/v1/preauthkey.proto";
|
import "headscale/v1/preauthkey.proto";
|
||||||
import "headscale/v1/machine.proto";
|
import "headscale/v1/machine.proto";
|
||||||
import "headscale/v1/routes.proto";
|
import "headscale/v1/routes.proto";
|
||||||
|
import "headscale/v1/apikey.proto";
|
||||||
// import "headscale/v1/device.proto";
|
// import "headscale/v1/device.proto";
|
||||||
|
|
||||||
service HeadscaleService {
|
service HeadscaleService {
|
||||||
@@ -131,6 +132,28 @@ service HeadscaleService {
|
|||||||
}
|
}
|
||||||
// --- Route end ---
|
// --- Route end ---
|
||||||
|
|
||||||
|
// --- ApiKeys start ---
|
||||||
|
rpc CreateApiKey(CreateApiKeyRequest) returns (CreateApiKeyResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/api/v1/apikey"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc ExpireApiKey(ExpireApiKeyRequest) returns (ExpireApiKeyResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/api/v1/apikey/expire"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc ListApiKeys(ListApiKeysRequest) returns (ListApiKeysResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/api/v1/apikey"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// --- ApiKeys end ---
|
||||||
|
|
||||||
// Implement Tailscale API
|
// Implement Tailscale API
|
||||||
// rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) {
|
// rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) {
|
||||||
// option(google.api.http) = {
|
// option(google.api.http) = {
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ enum RegisterMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message Machine {
|
message Machine {
|
||||||
uint64 id = 1;
|
uint64 id = 1;
|
||||||
string machine_key = 2;
|
string machine_key = 2;
|
||||||
string node_key = 3;
|
string node_key = 3;
|
||||||
string disco_key = 4;
|
string disco_key = 4;
|
||||||
string ip_address = 5;
|
repeated string ip_addresses = 5;
|
||||||
string name = 6;
|
string name = 6;
|
||||||
Namespace namespace = 7;
|
Namespace namespace = 7;
|
||||||
|
|
||||||
bool registered = 8;
|
bool registered = 8;
|
||||||
RegisterMethod register_method = 9;
|
RegisterMethod register_method = 9;
|
||||||
|
|||||||
@@ -143,10 +143,5 @@ func (h *Headscale) EnableNodeRoute(
|
|||||||
machine.EnabledRoutes = datatypes.JSON(routes)
|
machine.EnabledRoutes = datatypes.JSON(routes)
|
||||||
h.db.Save(&machine)
|
h.db.Save(&machine)
|
||||||
|
|
||||||
err = h.RequestMapUpdates(machine.NamespaceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,11 +67,6 @@ func (h *Headscale) RemoveSharedMachineFromNamespace(
|
|||||||
return errMachineNotShared
|
return errMachineNotShared
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.RequestMapUpdates(namespace.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateNodeNamespace(
|
func CreateNodeNamespace(
|
||||||
@@ -26,7 +27,7 @@ func CreateNodeNamespace(
|
|||||||
NamespaceID: namespace.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: ip,
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||||
AuthKeyID: uint(pak1.ID),
|
AuthKeyID: uint(pak1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine)
|
app.db.Save(machine)
|
||||||
@@ -214,7 +215,7 @@ func (s *Suite) TestComplexSharingAcrossNamespaces(c *check.C) {
|
|||||||
NamespaceID: namespace1.ID,
|
NamespaceID: namespace1.ID,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(pak4.ID),
|
AuthKeyID: uint(pak4.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine4)
|
app.db.Save(machine4)
|
||||||
@@ -294,7 +295,7 @@ func (s *Suite) TestDeleteSharedMachine(c *check.C) {
|
|||||||
NamespaceID: namespace1.ID,
|
NamespaceID: namespace1.ID,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||||
AuthKeyID: uint(pak4n1.ID),
|
AuthKeyID: uint(pak4n1.ID),
|
||||||
}
|
}
|
||||||
app.db.Save(machine4)
|
app.db.Save(machine4)
|
||||||
|
|||||||
115
utils.go
115
utils.go
@@ -7,6 +7,8 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -133,66 +135,93 @@ func encode(
|
|||||||
return privKey.SealTo(*pubKey, b), nil
|
return privKey.SealTo(*pubKey, b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getAvailableIP() (*netaddr.IP, error) {
|
func (h *Headscale) getAvailableIPs() (ips MachineAddresses, err error) {
|
||||||
ipPrefix := h.cfg.IPPrefix
|
ipPrefixes := h.cfg.IPPrefixes
|
||||||
|
for _, ipPrefix := range ipPrefixes {
|
||||||
|
var ip *netaddr.IP
|
||||||
|
ip, err = h.getAvailableIP(ipPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ips = append(ips, *ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIPPrefixEndpoints(na netaddr.IPPrefix) (network, broadcast netaddr.IP) {
|
||||||
|
ipRange := na.Range()
|
||||||
|
network = ipRange.From()
|
||||||
|
broadcast = ipRange.To()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is this concurrency safe?
|
||||||
|
// What would happen if multiple hosts were to register at the same time?
|
||||||
|
// Would we attempt to assign the same addresses to multiple nodes?
|
||||||
|
func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, error) {
|
||||||
usedIps, err := h.getUsedIPs()
|
usedIps, err := h.getUsedIPs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipPrefixNetworkAddress, ipPrefixBroadcastAddress := GetIPPrefixEndpoints(ipPrefix)
|
||||||
|
|
||||||
// Get the first IP in our prefix
|
// Get the first IP in our prefix
|
||||||
ip := ipPrefix.IP()
|
ip := ipPrefixNetworkAddress.Next()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if !ipPrefix.Contains(ip) {
|
if !ipPrefix.Contains(ip) {
|
||||||
return nil, errCouldNotAllocateIP
|
return nil, errCouldNotAllocateIP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some OS (including Linux) does not like when IPs ends with 0 or 255, which
|
switch {
|
||||||
// is typically called network or broadcast. Lets avoid them and continue
|
case ip.Compare(ipPrefixBroadcastAddress) == 0:
|
||||||
// to look when we get one of those traditionally reserved IPs.
|
fallthrough
|
||||||
ipRaw := ip.As4()
|
case containsIPs(usedIps, ip):
|
||||||
if ipRaw[3] == 0 || ipRaw[3] == 255 {
|
fallthrough
|
||||||
|
case ip.IsZero() || ip.IsLoopback():
|
||||||
ip = ip.Next()
|
ip = ip.Next()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
if ip.IsZero() &&
|
default:
|
||||||
ip.IsLoopback() {
|
|
||||||
ip = ip.Next()
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !containsIPs(usedIps, ip) {
|
|
||||||
return &ip, nil
|
return &ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = ip.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
|
func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
|
||||||
var addresses []string
|
// FIXME: This really deserves a better data model,
|
||||||
h.db.Model(&Machine{}).Pluck("ip_address", &addresses)
|
// but this was quick to get running and it should be enough
|
||||||
|
// to begin experimenting with a dual stack tailnet.
|
||||||
|
var addressesSlices []string
|
||||||
|
h.db.Model(&Machine{}).Pluck("ip_addresses", &addressesSlices)
|
||||||
|
|
||||||
ips := make([]netaddr.IP, len(addresses))
|
ips := make([]netaddr.IP, 0, len(h.cfg.IPPrefixes)*len(addressesSlices))
|
||||||
for index, addr := range addresses {
|
for _, slice := range addressesSlices {
|
||||||
if addr != "" {
|
var a MachineAddresses
|
||||||
ip, err := netaddr.ParseIP(addr)
|
err := a.Scan(slice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse ip from database: %w", err)
|
return nil, fmt.Errorf("failed to read ip from database: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
ips[index] = ip
|
|
||||||
}
|
}
|
||||||
|
ips = append(ips, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containsString(ss []string, s string) bool {
|
||||||
|
for _, v := range ss {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func containsIPs(ips []netaddr.IP, ip netaddr.IP) bool {
|
func containsIPs(ips []netaddr.IP, ip netaddr.IP) bool {
|
||||||
for _, v := range ips {
|
for _, v := range ips {
|
||||||
if v == ip {
|
if v == ip {
|
||||||
@@ -261,3 +290,29 @@ func containsIPPrefix(prefixes []netaddr.IPPrefix, prefix netaddr.IPPrefix) bool
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateRandomBytes returns securely generated random bytes.
|
||||||
|
// It will return an error if the system's secure random
|
||||||
|
// number generator fails to function correctly, in which
|
||||||
|
// case the caller should not continue.
|
||||||
|
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||||
|
bytes := make([]byte, n)
|
||||||
|
|
||||||
|
// Note that err == nil only if we read len(b) bytes.
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded
|
||||||
|
// securely generated random string.
|
||||||
|
// It will return an error if the system's secure random
|
||||||
|
// number generator fails to function correctly, in which
|
||||||
|
// case the caller should not continue.
|
||||||
|
func GenerateRandomStringURLSafe(n int) (string, error) {
|
||||||
|
b, err := GenerateRandomBytes(n)
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b), err
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestGetAvailableIp(c *check.C) {
|
func (s *Suite) TestGetAvailableIp(c *check.C) {
|
||||||
ip, err := app.getAvailableIP()
|
ips, err := app.getAvailableIPs()
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
expected := netaddr.MustParseIP("10.27.0.1")
|
expected := netaddr.MustParseIP("10.27.0.1")
|
||||||
|
|
||||||
c.Assert(ip.String(), check.Equals, expected.String())
|
c.Assert(len(ips), check.Equals, 1)
|
||||||
|
c.Assert(ips[0].String(), check.Equals, expected.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestGetUsedIps(c *check.C) {
|
func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||||
ip, err := app.getAvailableIP()
|
ips, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
namespace, err := app.CreateNamespace("test_ip")
|
namespace, err := app.CreateNamespace("test_ip")
|
||||||
@@ -38,22 +39,24 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
|||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
IPAddress: ip.String(),
|
IPAddresses: ips,
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
ips, err := app.getUsedIPs()
|
usedIps, err := app.getUsedIPs()
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
expected := netaddr.MustParseIP("10.27.0.1")
|
expected := netaddr.MustParseIP("10.27.0.1")
|
||||||
|
|
||||||
c.Assert(ips[0], check.Equals, expected)
|
c.Assert(len(usedIps), check.Equals, 1)
|
||||||
|
c.Assert(usedIps[0], check.Equals, expected)
|
||||||
|
|
||||||
machine1, err := app.GetMachineByID(0)
|
machine1, err := app.GetMachineByID(0)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
c.Assert(machine1.IPAddress, check.Equals, expected.String())
|
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||||
|
c.Assert(machine1.IPAddresses[0], check.Equals, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestGetMultiIp(c *check.C) {
|
func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||||
@@ -61,7 +64,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
for index := 1; index <= 350; index++ {
|
for index := 1; index <= 350; index++ {
|
||||||
ip, err := app.getAvailableIP()
|
ips, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||||
@@ -80,59 +83,64 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
|||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: RegisterMethodAuthKey,
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
IPAddress: ip.String(),
|
IPAddresses: ips,
|
||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := app.getUsedIPs()
|
usedIps, err := app.getUsedIPs()
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
c.Assert(len(ips), check.Equals, 350)
|
c.Assert(len(usedIps), check.Equals, 350)
|
||||||
|
|
||||||
c.Assert(ips[0], check.Equals, netaddr.MustParseIP("10.27.0.1"))
|
c.Assert(usedIps[0], check.Equals, netaddr.MustParseIP("10.27.0.1"))
|
||||||
c.Assert(ips[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
|
c.Assert(usedIps[9], check.Equals, netaddr.MustParseIP("10.27.0.10"))
|
||||||
c.Assert(ips[300], check.Equals, netaddr.MustParseIP("10.27.1.47"))
|
c.Assert(usedIps[300], check.Equals, netaddr.MustParseIP("10.27.1.45"))
|
||||||
|
|
||||||
// Check that we can read back the IPs
|
// Check that we can read back the IPs
|
||||||
machine1, err := app.GetMachineByID(1)
|
machine1, err := app.GetMachineByID(1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(len(machine1.IPAddresses), check.Equals, 1)
|
||||||
c.Assert(
|
c.Assert(
|
||||||
machine1.IPAddress,
|
machine1.IPAddresses[0],
|
||||||
check.Equals,
|
check.Equals,
|
||||||
netaddr.MustParseIP("10.27.0.1").String(),
|
netaddr.MustParseIP("10.27.0.1"),
|
||||||
)
|
)
|
||||||
|
|
||||||
machine50, err := app.GetMachineByID(50)
|
machine50, err := app.GetMachineByID(50)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
c.Assert(len(machine50.IPAddresses), check.Equals, 1)
|
||||||
c.Assert(
|
c.Assert(
|
||||||
machine50.IPAddress,
|
machine50.IPAddresses[0],
|
||||||
check.Equals,
|
check.Equals,
|
||||||
netaddr.MustParseIP("10.27.0.50").String(),
|
netaddr.MustParseIP("10.27.0.50"),
|
||||||
)
|
)
|
||||||
|
|
||||||
expectedNextIP := netaddr.MustParseIP("10.27.1.97")
|
expectedNextIP := netaddr.MustParseIP("10.27.1.95")
|
||||||
nextIP, err := app.getAvailableIP()
|
nextIP, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
c.Assert(nextIP.String(), check.Equals, expectedNextIP.String())
|
c.Assert(len(nextIP), check.Equals, 1)
|
||||||
|
c.Assert(nextIP[0].String(), check.Equals, expectedNextIP.String())
|
||||||
|
|
||||||
// If we call get Available again, we should receive
|
// If we call get Available again, we should receive
|
||||||
// the same IP, as it has not been reserved.
|
// the same IP, as it has not been reserved.
|
||||||
nextIP2, err := app.getAvailableIP()
|
nextIP2, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
c.Assert(nextIP2.String(), check.Equals, expectedNextIP.String())
|
c.Assert(len(nextIP2), check.Equals, 1)
|
||||||
|
c.Assert(nextIP2[0].String(), check.Equals, expectedNextIP.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
||||||
ip, err := app.getAvailableIP()
|
ips, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
expected := netaddr.MustParseIP("10.27.0.1")
|
expected := netaddr.MustParseIP("10.27.0.1")
|
||||||
|
|
||||||
c.Assert(ip.String(), check.Equals, expected.String())
|
c.Assert(len(ips), check.Equals, 1)
|
||||||
|
c.Assert(ips[0].String(), check.Equals, expected.String())
|
||||||
|
|
||||||
namespace, err := app.CreateNamespace("test_ip")
|
namespace, err := app.CreateNamespace("test_ip")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@@ -156,8 +164,9 @@ func (s *Suite) TestGetAvailableIpMachineWithoutIP(c *check.C) {
|
|||||||
}
|
}
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
ip2, err := app.getAvailableIP()
|
ips2, err := app.getAvailableIPs()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
c.Assert(ip2.String(), check.Equals, expected.String())
|
c.Assert(len(ips2), check.Equals, 1)
|
||||||
|
c.Assert(ips2[0].String(), check.Equals, expected.String())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user