mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-22 15:57:31 +00:00
Compare commits
104 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
07f6bad067 | ||
![]() |
9c8131b967 | ||
![]() |
3454e03331 | ||
![]() |
e84bda4fe6 | ||
![]() |
f4152a166e | ||
![]() |
f3b99e31e4 | ||
![]() |
d5cc5b2bc8 | ||
![]() |
51abf90db6 | ||
![]() |
71410cb6da | ||
![]() |
efb12f208c | ||
![]() |
64ede5dbef | ||
![]() |
7af78152a4 | ||
![]() |
290ec8bb19 | ||
![]() |
cdf48b1216 | ||
![]() |
a24710a961 | ||
![]() |
197da8afcb | ||
![]() |
12385d4357 | ||
![]() |
e7f8bb866f | ||
![]() |
1ad19a3bd8 | ||
![]() |
cc0bec15ef | ||
![]() |
20970b580a | ||
![]() |
53857d418a | ||
![]() |
a81a4d274f | ||
![]() |
ce4a1cf447 | ||
![]() |
35dd9209b9 | ||
![]() |
81f91f03b4 | ||
![]() |
84a5edf345 | ||
![]() |
4aafe6c9d1 | ||
![]() |
3ab1487641 | ||
![]() |
71d22dc994 | ||
![]() |
4424a9abc0 | ||
![]() |
e20e818a42 | ||
![]() |
f0a8a2857b | ||
![]() |
175dfa1ede | ||
![]() |
04e4fa785b | ||
![]() |
6aec520889 | ||
![]() |
e9906b522f | ||
![]() |
2f554133c5 | ||
![]() |
922b8b5365 | ||
![]() |
c894db3dd4 | ||
![]() |
e43713a866 | ||
![]() |
b6e3cd81c6 | ||
![]() |
43ad0d4416 | ||
![]() |
a33b5a5c00 | ||
![]() |
e2bffd4f5a | ||
![]() |
a87a9636e3 | ||
![]() |
9d430d3c72 | ||
![]() |
f9a2a2b57a | ||
![]() |
e4d961cfad | ||
![]() |
67ffebc30a | ||
![]() |
cf731fafab | ||
![]() |
f43a83aad7 | ||
![]() |
7185f8dfea | ||
![]() |
8a707de5f1 | ||
![]() |
ba07bac46a | ||
![]() |
b71a881d0e | ||
![]() |
ce53bb0eee | ||
![]() |
c0fe1abf4d | ||
![]() |
0db7fc5ab7 | ||
![]() |
eb461d0713 | ||
![]() |
128ec6717c | ||
![]() |
b3cf5289f8 | ||
![]() |
c701f9e817 | ||
![]() |
865f1ffb3c | ||
![]() |
8db7629edf | ||
![]() |
b8980b9ed3 | ||
![]() |
5cf9eedf42 | ||
![]() |
f599bea216 | ||
![]() |
704a19b0a5 | ||
![]() |
e29b344e0f | ||
![]() |
7cc227d01e | ||
![]() |
df8ecdb603 | ||
![]() |
f4bab6b290 | ||
![]() |
35f3dee1d0 | ||
![]() |
db89fdea23 | ||
![]() |
d0898ecabc | ||
![]() |
e640c6df05 | ||
![]() |
ab18c721bb | ||
![]() |
aaa33cf093 | ||
![]() |
0f09e19e38 | ||
![]() |
b301405f24 | ||
![]() |
1f3032ad21 | ||
![]() |
c10142f767 | ||
![]() |
0d0042b7e6 | ||
![]() |
78a179c971 | ||
![]() |
cab828c9d4 | ||
![]() |
ff46f3ff49 | ||
![]() |
b67cff50f5 | ||
![]() |
20d2615081 | ||
![]() |
eb8d8f142c | ||
![]() |
3bea20850a | ||
![]() |
ade1b73779 | ||
![]() |
281ae59b5a | ||
![]() |
9994fce9d5 | ||
![]() |
39b85b02bb | ||
![]() |
7a91c82cda | ||
![]() |
c7cea9ef16 | ||
![]() |
1880035f6f | ||
![]() |
fdd0c50402 | ||
![]() |
be24bacb79 | ||
![]() |
b261d19cfe | ||
![]() |
014e7abc68 | ||
![]() |
3e8f0e9984 | ||
![]() |
6e8e2bf508 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.18.0
|
||||
go-version: 1.19.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
@@ -1,7 +1,7 @@
|
||||
---
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -compat=1.18
|
||||
- go mod tidy -compat=1.19
|
||||
|
||||
release:
|
||||
prerelease: auto
|
||||
|
@@ -2,6 +2,10 @@
|
||||
|
||||
## 0.17.0 (2022-XX-XX)
|
||||
|
||||
- Added support for Tailscale TS2021 protocol [#738](https://github.com/juanfont/headscale/pull/738)
|
||||
- Add ability to specify config location via env var `HEADSCALE_CONFIG` [#674](https://github.com/juanfont/headscale/issues/674)
|
||||
- Use Go 1.19 for Headscale [#776](https://github.com/juanfont/headscale/pull/776)
|
||||
|
||||
## 0.16.4 (2022-08-21)
|
||||
|
||||
### Changes
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Builder image
|
||||
FROM docker.io/golang:1.18.0-bullseye AS build
|
||||
FROM docker.io/golang:1.19.0-bullseye AS build
|
||||
ARG VERSION=dev
|
||||
ENV GOPATH /go
|
||||
WORKDIR /go/src/headscale
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Builder image
|
||||
FROM docker.io/golang:1.18.0-alpine AS build
|
||||
FROM docker.io/golang:1.19.0-alpine AS build
|
||||
ARG VERSION=dev
|
||||
ENV GOPATH /go
|
||||
WORKDIR /go/src/headscale
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Builder image
|
||||
FROM docker.io/golang:1.18.0-bullseye AS build
|
||||
FROM docker.io/golang:1.19.0-bullseye AS build
|
||||
ARG VERSION=dev
|
||||
ENV GOPATH /go
|
||||
WORKDIR /go/src/headscale
|
||||
|
@@ -7,7 +7,9 @@ RUN apt-get update \
|
||||
|
||||
RUN git clone https://github.com/tailscale/tailscale.git
|
||||
|
||||
WORKDIR tailscale
|
||||
WORKDIR /go/tailscale
|
||||
|
||||
RUN git checkout main
|
||||
|
||||
RUN sh build_dist.sh tailscale.com/cmd/tailscale
|
||||
RUN sh build_dist.sh tailscale.com/cmd/tailscaled
|
||||
|
64
README.md
64
README.md
@@ -269,6 +269,13 @@ make build
|
||||
<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">
|
||||
<a href=https://github.com/617a7a>
|
||||
<img src=https://avatars.githubusercontent.com/u/67651251?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Azz/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>Azz</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<a href=https://github.com/iSchluff>
|
||||
<img src=https://avatars.githubusercontent.com/u/1429641?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Anton Schubert/>
|
||||
@@ -283,6 +290,15 @@ make build
|
||||
<sub style="font-size:14px"><b>Aaron Bieber</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/Aluxima>
|
||||
<img src=https://avatars.githubusercontent.com/u/16262531?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Laurent Marchaud/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>Laurent Marchaud</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<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/>
|
||||
@@ -290,8 +306,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Fernando De Lucchi</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/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/>
|
||||
@@ -320,6 +334,8 @@ make build
|
||||
<sub style="font-size:14px"><b>ChibangLW</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/mevansam>
|
||||
<img src=https://avatars.githubusercontent.com/u/403630?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Mevan Samaratunga/>
|
||||
@@ -334,8 +350,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Michael G.</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/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/>
|
||||
@@ -364,6 +378,8 @@ make build
|
||||
<sub style="font-size:14px"><b>Artem Klevtsov</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/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/>
|
||||
@@ -378,8 +394,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Pavlos Vinieratos</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/SilverBut>
|
||||
<img src=https://avatars.githubusercontent.com/u/6560655?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Silver Bullet/>
|
||||
@@ -387,6 +401,13 @@ make build
|
||||
<sub style="font-size:14px"><b>Silver Bullet</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<a href=https://github.com/vtrf>
|
||||
<img src=https://avatars.githubusercontent.com/u/25647735?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Victor Freire/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>Victor Freire</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<a href=https://github.com/lachy2849>
|
||||
<img src=https://avatars.githubusercontent.com/u/98844035?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=lachy2849/>
|
||||
@@ -401,6 +422,8 @@ make build
|
||||
<sub style="font-size:14px"><b>thomas</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/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/>
|
||||
@@ -422,8 +445,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Aofei Sheng</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/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/>
|
||||
@@ -445,6 +466,8 @@ make build
|
||||
<sub style="font-size:14px"><b> Carson Yang</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/kundel>
|
||||
<img src=https://avatars.githubusercontent.com/u/10158899?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=kundel/>
|
||||
@@ -466,8 +489,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Felix Yan</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/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/>
|
||||
@@ -489,6 +510,8 @@ make build
|
||||
<sub style="font-size:14px"><b>Jim Tittsler</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/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/>
|
||||
@@ -510,8 +533,6 @@ make build
|
||||
<sub style="font-size:14px"><b>rcursaru</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/renovate-bot>
|
||||
<img src=https://avatars.githubusercontent.com/u/25180681?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=WhiteSource Renovate/>
|
||||
@@ -533,6 +554,8 @@ make build
|
||||
<sub style="font-size:14px"><b>Shaanan Cohney</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/sophware>
|
||||
<img src=https://avatars.githubusercontent.com/u/41669?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=sophware/>
|
||||
@@ -554,8 +577,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Teteros</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/gitter-badger>
|
||||
<img src=https://avatars.githubusercontent.com/u/8518239?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=The Gitter Badger/>
|
||||
@@ -577,6 +598,8 @@ make build
|
||||
<sub style="font-size:14px"><b>Tjerk Woudsma</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/y0ngb1n>
|
||||
<img src=https://avatars.githubusercontent.com/u/25719408?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Yang Bin/>
|
||||
@@ -584,6 +607,13 @@ make build
|
||||
<sub style="font-size:14px"><b>Yang Bin</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<a href=https://github.com/gozssky>
|
||||
<img src=https://avatars.githubusercontent.com/u/17199941?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Yujie Xia/>
|
||||
<br />
|
||||
<sub style="font-size:14px"><b>Yujie Xia</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||
<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/>
|
||||
@@ -598,8 +628,6 @@ make build
|
||||
<sub style="font-size:14px"><b>Ziyuan Han</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/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/>
|
||||
@@ -614,6 +642,8 @@ make build
|
||||
<sub style="font-size:14px"><b>henning mueller</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/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/>
|
||||
@@ -642,8 +672,6 @@ make build
|
||||
<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">
|
||||
<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/>
|
||||
|
110
acls_test.go
110
acls_test.go
@@ -2,11 +2,11 @@ package headscale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
@@ -131,7 +131,7 @@ func (s *Suite) TestValidExpandTagOwnersInSources(c *check.C) {
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
@@ -181,7 +181,7 @@ func (s *Suite) TestValidExpandTagOwnersInDestinations(c *check.C) {
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
@@ -231,7 +231,7 @@ func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
@@ -280,7 +280,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
|
||||
NodeKey: "bar",
|
||||
DiscoKey: "faa",
|
||||
Hostname: "webserver",
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
@@ -299,7 +299,7 @@ func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
|
||||
NodeKey: "bar2",
|
||||
DiscoKey: "faab",
|
||||
Hostname: "user",
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
@@ -844,10 +844,10 @@ func Test_expandAlias(t *testing.T) {
|
||||
args: args{
|
||||
alias: "*",
|
||||
machines: []Machine{
|
||||
{IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}},
|
||||
{IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")}},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.78.84.227"),
|
||||
netip.MustParseAddr("100.78.84.227"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -864,25 +864,25 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -902,25 +902,25 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -951,7 +951,7 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{},
|
||||
aclPolicy: ACLPolicy{
|
||||
Hosts: Hosts{
|
||||
"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
|
||||
"homeNetwork": netip.MustParsePrefix("192.168.1.0/24"),
|
||||
},
|
||||
},
|
||||
stripEmailDomain: true,
|
||||
@@ -988,7 +988,7 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -999,7 +999,7 @@ func Test_expandAlias(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1010,13 +1010,13 @@ func Test_expandAlias(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1036,25 +1036,25 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -1077,27 +1077,27 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
ForcedTags: []string{"tag:hr-webserver"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
ForcedTags: []string{"tag:hr-webserver"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -1115,14 +1115,14 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
ForcedTags: []string{"tag:hr-webserver"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1133,13 +1133,13 @@ func Test_expandAlias(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -1161,7 +1161,7 @@ func Test_expandAlias(t *testing.T) {
|
||||
machines: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1172,7 +1172,7 @@ func Test_expandAlias(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1183,13 +1183,13 @@ func Test_expandAlias(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1245,7 +1245,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
nodes: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1256,7 +1256,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1267,7 +1267,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1277,7 +1277,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
want: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
},
|
||||
@@ -1296,7 +1296,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
nodes: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1307,7 +1307,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1318,7 +1318,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1328,7 +1328,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
want: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
},
|
||||
@@ -1342,7 +1342,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
nodes: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1353,14 +1353,14 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
ForcedTags: []string{"tag:accountant-webserver"},
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1370,7 +1370,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
want: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.4")},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
},
|
||||
@@ -1384,7 +1384,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
nodes: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1395,7 +1395,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1406,7 +1406,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -1417,7 +1417,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
want: []Machine{
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1428,7 +1428,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
HostInfo: HostInfo{
|
||||
@@ -1439,7 +1439,7 @@ func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.4"),
|
||||
netip.MustParseAddr("100.64.0.4"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
|
@@ -2,11 +2,11 @@ package headscale
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/tailscale/hujson"
|
||||
"gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// ACLPolicy represents a Tailscale ACL Policy.
|
||||
@@ -30,7 +30,7 @@ type ACL struct {
|
||||
type Groups map[string][]string
|
||||
|
||||
// Hosts are alias for IP addresses or subnets.
|
||||
type Hosts map[string]netaddr.IPPrefix
|
||||
type Hosts map[string]netip.Prefix
|
||||
|
||||
// TagOwners specify what users (namespaces?) are allow to use certain tags.
|
||||
type TagOwners map[string][]string
|
||||
@@ -60,7 +60,7 @@ func (hosts *Hosts) UnmarshalJSON(data []byte) error {
|
||||
if !strings.Contains(prefixStr, "/") {
|
||||
prefixStr += "/32"
|
||||
}
|
||||
prefix, err := netaddr.ParseIPPrefix(prefixStr)
|
||||
prefix, err := netip.ParsePrefix(prefixStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func (hosts *Hosts) UnmarshalYAML(data []byte) error {
|
||||
return err
|
||||
}
|
||||
for host, prefixStr := range hostIPPrefixMap {
|
||||
prefix, err := netaddr.ParseIPPrefix(prefixStr)
|
||||
prefix, err := netip.ParsePrefix(prefixStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
784
api.go
784
api.go
@@ -2,22 +2,13 @@ package headscale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -70,23 +61,6 @@ func (h *Headscale) HealthHandler(
|
||||
respond(nil)
|
||||
}
|
||||
|
||||
// KeyHandler provides the Headscale pub key
|
||||
// Listens in /key.
|
||||
func (h *Headscale) KeyHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err := writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public())))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
type registerWebAPITemplateConfig struct {
|
||||
Key string
|
||||
}
|
||||
@@ -164,761 +138,3 @@ func (h *Headscale) RegisterWebAPI(
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
// RegistrationHandler handles the actual registration process of a machine
|
||||
// Endpoint /machine/:mkey.
|
||||
func (h *Headscale) RegistrationHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
vars := mux.Vars(req)
|
||||
machineKeyStr, ok := vars["mkey"]
|
||||
if !ok || machineKeyStr == "" {
|
||||
log.Error().
|
||||
Str("handler", "RegistrationHandler").
|
||||
Msg("No machine ID in request")
|
||||
http.Error(writer, "No machine ID in request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse machine key")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot parse machine key", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
registerRequest := tailcfg.RegisterRequest{}
|
||||
err = decode(body, ®isterRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot decode message")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot decode message", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||
|
||||
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
h.handleAuthKey(writer, req, machineKey, registerRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the node is waiting for interactive login.
|
||||
//
|
||||
// TODO(juan): We could use this field to improve our protocol implementation,
|
||||
// and hold the request until the client closes it, or the interactive
|
||||
// login is completed (i.e., the user registers the machine).
|
||||
// This is not implemented yet, as it is no strictly required. The only side-effect
|
||||
// is that the client will hammer headscale with requests until it gets a
|
||||
// successful RegisterResponse.
|
||||
if registerRequest.Followup != "" {
|
||||
if _, ok := h.registrationCache.Get(NodePublicKeyStripPrefix(registerRequest.NodeKey)); ok {
|
||||
log.Debug().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Msg("Machine is waiting for interactive login")
|
||||
|
||||
ticker := time.NewTicker(registrationHoldoff)
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
h.handleMachineRegistrationNew(writer, req, machineKey, registerRequest)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Msg("New machine not yet in the database")
|
||||
|
||||
givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RegistrationHandler").
|
||||
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
|
||||
Err(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine did not have a key to authenticate, which means
|
||||
// that we rely on a method that calls back some how (OpenID or CLI)
|
||||
// We create the machine and then keep it around until a callback
|
||||
// happens
|
||||
newMachine := Machine{
|
||||
MachineKey: machineKeyStr,
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
NodeKey: NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
LastSeen: &now,
|
||||
Expiry: &time.Time{},
|
||||
}
|
||||
|
||||
if !registerRequest.Expiry.IsZero() {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Time("expiry", registerRequest.Expiry).
|
||||
Msg("Non-zero expiry time requested")
|
||||
newMachine.Expiry = ®isterRequest.Expiry
|
||||
}
|
||||
|
||||
h.registrationCache.Set(
|
||||
newMachine.NodeKey,
|
||||
newMachine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
h.handleMachineRegistrationNew(writer, req, machineKey, registerRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine is already registered, so we need to pass through reauth or key update.
|
||||
if machine != nil {
|
||||
// If the NodeKey stored in headscale is the same as the key presented in a registration
|
||||
// request, then we have a node that is either:
|
||||
// - Trying to log out (sending a expiry in the past)
|
||||
// - A valid, registered machine, looking for the node map
|
||||
// - Expired machine wanting to reauthenticate
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||
if !registerRequest.Expiry.IsZero() &&
|
||||
registerRequest.Expiry.UTC().Before(now) {
|
||||
h.handleMachineLogOut(writer, req, machineKey, *machine)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If machine is not expired, and is register, we have a already accepted this machine,
|
||||
// let it proceed with a valid registration
|
||||
if !machine.isExpired() {
|
||||
h.handleMachineValidRegistration(writer, req, machineKey, *machine)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
!machine.isExpired() {
|
||||
h.handleMachineRefreshKey(
|
||||
writer,
|
||||
req,
|
||||
machineKey,
|
||||
registerRequest,
|
||||
*machine,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine has expired
|
||||
h.handleMachineExpired(writer, req, machineKey, registerRequest, *machine)
|
||||
|
||||
machine.Expiry = &time.Time{}
|
||||
h.registrationCache.Set(
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
*machine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) getMapResponse(
|
||||
machineKey key.MachinePublic,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *Machine,
|
||||
) ([]byte, error) {
|
||||
log.Trace().
|
||||
Str("func", "getMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
Msg("Creating Map response")
|
||||
node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "getMapResponse").
|
||||
Err(err).
|
||||
Msg("Cannot convert to node")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peers, err := h.getValidPeers(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "getMapResponse").
|
||||
Err(err).
|
||||
Msg("Cannot fetch peers")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profiles := getMapResponseUserProfiles(*machine, peers)
|
||||
|
||||
nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "getMapResponse").
|
||||
Err(err).
|
||||
Msg("Failed to convert peers to Tailscale nodes")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsConfig := getMapResponseDNSConfig(
|
||||
h.cfg.DNSConfig,
|
||||
h.cfg.BaseDomain,
|
||||
*machine,
|
||||
peers,
|
||||
)
|
||||
|
||||
resp := tailcfg.MapResponse{
|
||||
KeepAlive: false,
|
||||
Node: node,
|
||||
Peers: nodePeers,
|
||||
DNSConfig: dnsConfig,
|
||||
Domain: h.cfg.BaseDomain,
|
||||
PacketFilter: h.aclRules,
|
||||
DERPMap: h.DERPMap,
|
||||
UserProfiles: profiles,
|
||||
Debug: &tailcfg.Debug{
|
||||
DisableLogTail: !h.cfg.LogTail.Enabled,
|
||||
RandomizeClientPort: h.cfg.RandomizeClientPort,
|
||||
},
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("func", "getMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
// Interface("payload", resp).
|
||||
Msgf("Generated map response: %s", tailMapResponseToString(resp))
|
||||
|
||||
var respBody []byte
|
||||
if mapRequest.Compress == "zstd" {
|
||||
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)
|
||||
srcCompressed := encoder.EncodeAll(src, nil)
|
||||
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
||||
} else {
|
||||
respBody, err = encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// declare the incoming size on the first 4 bytes
|
||||
data := make([]byte, reservedResponseHeaderSize)
|
||||
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
||||
data = append(data, respBody...)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) getMapKeepAliveResponse(
|
||||
machineKey key.MachinePublic,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
) ([]byte, error) {
|
||||
mapResponse := tailcfg.MapResponse{
|
||||
KeepAlive: true,
|
||||
}
|
||||
var respBody []byte
|
||||
var err error
|
||||
if mapRequest.Compress == "zstd" {
|
||||
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)
|
||||
srcCompressed := encoder.EncodeAll(src, nil)
|
||||
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
||||
} else {
|
||||
respBody, err = encode(mapResponse, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
data := make([]byte, reservedResponseHeaderSize)
|
||||
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
||||
data = append(data, respBody...)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineLogOut(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
machine Machine,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
log.Info().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client requested logout")
|
||||
|
||||
err := h.ExpireMachine(&machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleMachineLogOut").
|
||||
Err(err).
|
||||
Msg("Failed to expire machine")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = false
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineValidRegistration(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
machine Machine,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is valid, respond with redirect to /map
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = true
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
resp.Login = *machine.Namespace.toLogin()
|
||||
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineExpired(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine Machine,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The client has registered before, but has expired
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine registration has expired. Sending a authurl to register")
|
||||
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
h.handleAuthKey(writer, req, machineKey, registerRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if h.cfg.OIDC.Issuer != "" {
|
||||
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
} else {
|
||||
resp.AuthURL = fmt.Sprintf("%s/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
}
|
||||
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineRefreshKey(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine Machine,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
log.Debug().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
||||
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to update machine key in the database")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineRegistrationNew(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is new, redirect the client to the registration URL
|
||||
log.Debug().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("The node seems to be new, sending auth url")
|
||||
if h.cfg.OIDC.Issuer != "" {
|
||||
resp.AuthURL = fmt.Sprintf(
|
||||
"%s/oidc/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
)
|
||||
} else {
|
||||
resp.AuthURL = fmt.Sprintf("%s/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
}
|
||||
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if any locks are needed around IP allocation.
|
||||
func (h *Headscale) handleAuthKey(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machineKey key.MachinePublic,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
) {
|
||||
machineKeyStr := MachinePublicKeyStripPrefix(machineKey)
|
||||
|
||||
log.Debug().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
resp.MachineAuthorized = false
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
|
||||
if pak != nil {
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
} else {
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", "unknown").Inc()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||
|
||||
nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
// retrieve machine information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.GetMachineByMachineKey(machineKey)
|
||||
if machine != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("machine already registered, refreshing with new auth key")
|
||||
|
||||
machine.NodeKey = nodeKey
|
||||
machine.AuthKeyID = uint(pak.ID)
|
||||
err := h.RefreshMachine(machine, registerRequest.Expiry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("machine", machine.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to refresh machine")
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
now := time.Now().UTC()
|
||||
|
||||
givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RegistrationHandler").
|
||||
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
|
||||
Err(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
machineToRegister := Machine{
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
NamespaceID: pak.Namespace.ID,
|
||||
MachineKey: machineKeyStr,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
Expiry: ®isterRequest.Expiry,
|
||||
NodeKey: nodeKey,
|
||||
LastSeen: &now,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
|
||||
machine, err = h.RegisterMachine(
|
||||
machineToRegister,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("could not register machine")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = h.UsePreAuthKey(pak)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to use pre-auth key")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.MachineAuthorized = true
|
||||
resp.User = *pak.Namespace.toUser()
|
||||
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name).
|
||||
Inc()
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("func", "handleAuthKey").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
|
||||
Msg("Successfully authenticated via AuthKey")
|
||||
}
|
||||
|
80
api_common.go
Normal file
80
api_common.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
func (h *Headscale) generateMapResponse(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *Machine,
|
||||
) (*tailcfg.MapResponse, error) {
|
||||
log.Trace().
|
||||
Str("func", "generateMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
Msg("Creating Map response")
|
||||
node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "generateMapResponse").
|
||||
Err(err).
|
||||
Msg("Cannot convert to node")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peers, err := h.getValidPeers(machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "generateMapResponse").
|
||||
Err(err).
|
||||
Msg("Cannot fetch peers")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profiles := getMapResponseUserProfiles(*machine, peers)
|
||||
|
||||
nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "generateMapResponse").
|
||||
Err(err).
|
||||
Msg("Failed to convert peers to Tailscale nodes")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dnsConfig := getMapResponseDNSConfig(
|
||||
h.cfg.DNSConfig,
|
||||
h.cfg.BaseDomain,
|
||||
*machine,
|
||||
peers,
|
||||
)
|
||||
|
||||
resp := tailcfg.MapResponse{
|
||||
KeepAlive: false,
|
||||
Node: node,
|
||||
Peers: nodePeers,
|
||||
DNSConfig: dnsConfig,
|
||||
Domain: h.cfg.BaseDomain,
|
||||
PacketFilter: h.aclRules,
|
||||
DERPMap: h.DERPMap,
|
||||
UserProfiles: profiles,
|
||||
Debug: &tailcfg.Debug{
|
||||
DisableLogTail: !h.cfg.LogTail.Enabled,
|
||||
RandomizeClientPort: h.cfg.RandomizeClientPort,
|
||||
},
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("func", "generateMapResponse").
|
||||
Str("machine", mapRequest.Hostinfo.Hostname).
|
||||
// Interface("payload", resp).
|
||||
Msgf("Generated map response: %s", tailMapResponseToString(resp))
|
||||
|
||||
return &resp, nil
|
||||
}
|
46
app.go
46
app.go
@@ -51,6 +51,10 @@ const (
|
||||
errUnsupportedLetsEncryptChallengeType = Error(
|
||||
"unknown value for Lets Encrypt challenge type",
|
||||
)
|
||||
|
||||
ErrFailedPrivateKey = Error("failed to read or create private key")
|
||||
ErrFailedNoisePrivateKey = Error("failed to read or create Noise protocol private key")
|
||||
ErrSamePrivateKeys = Error("private key and noise private key are the same")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -78,6 +82,9 @@ type Headscale struct {
|
||||
dbType string
|
||||
dbDebug bool
|
||||
privateKey *key.MachinePrivate
|
||||
noisePrivateKey *key.MachinePrivate
|
||||
|
||||
noiseMux *mux.Router
|
||||
|
||||
DERPMap *tailcfg.DERPMap
|
||||
DERPServer *DERPServer
|
||||
@@ -120,9 +127,19 @@ func LookupTLSClientAuthMode(mode string) (tls.ClientAuthType, bool) {
|
||||
}
|
||||
|
||||
func NewHeadscale(cfg *Config) (*Headscale, error) {
|
||||
privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
|
||||
privateKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read or create private key: %w", err)
|
||||
return nil, ErrFailedPrivateKey
|
||||
}
|
||||
|
||||
// TS2021 requires to have a different key from the legacy protocol.
|
||||
noisePrivateKey, err := readOrCreatePrivateKey(cfg.NoisePrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, ErrFailedNoisePrivateKey
|
||||
}
|
||||
|
||||
if privateKey.Equal(*noisePrivateKey) {
|
||||
return nil, ErrSamePrivateKeys
|
||||
}
|
||||
|
||||
var dbString string
|
||||
@@ -161,7 +178,8 @@ func NewHeadscale(cfg *Config) (*Headscale, error) {
|
||||
cfg: cfg,
|
||||
dbType: cfg.DBtype,
|
||||
dbString: dbString,
|
||||
privateKey: privKey,
|
||||
privateKey: privateKey,
|
||||
noisePrivateKey: noisePrivateKey,
|
||||
aclRules: tailcfg.FilterAllowAll, // default allowall
|
||||
registrationCache: registrationCache,
|
||||
pollNetMapStreamWG: sync.WaitGroup{},
|
||||
@@ -257,7 +275,7 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
||||
}
|
||||
|
||||
if expiredFound {
|
||||
h.setLastStateChangeToNow(namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,6 +443,8 @@ func (h *Headscale) ensureUnixSocketIsAbsent() error {
|
||||
func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc(ts2021UpgradePath, h.NoiseUpgradeHandler).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/health", h.HealthHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/key", h.KeyHandler).Methods(http.MethodGet)
|
||||
router.HandleFunc("/register/{nkey}", h.RegisterWebAPI).Methods(http.MethodGet)
|
||||
@@ -454,6 +474,15 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router {
|
||||
return router
|
||||
}
|
||||
|
||||
func (h *Headscale) createNoiseMux() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/machine/register", h.NoiseRegistrationHandler).Methods(http.MethodPost)
|
||||
router.HandleFunc("/machine/map", h.NoisePollNetMapHandler)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Serve launches a GIN server with the Headscale API.
|
||||
func (h *Headscale) Serve() error {
|
||||
var err error
|
||||
@@ -607,9 +636,16 @@ func (h *Headscale) Serve() error {
|
||||
//
|
||||
// HTTP setup
|
||||
//
|
||||
|
||||
// This is the regular router that we expose
|
||||
// over our main Addr. It also serves the legacy Tailcale API
|
||||
router := h.createRouter(grpcGatewayMux)
|
||||
|
||||
// This router is served only over the Noise connection, and exposes only the new API.
|
||||
//
|
||||
// The HTTP2 server that exposes this router is created for
|
||||
// a single hijacked connection from /ts2021, using netutil.NewOneConnListener
|
||||
h.noiseMux = h.createNoiseMux()
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: h.cfg.Addr,
|
||||
Handler: router,
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
@@ -39,8 +39,8 @@ func (s *Suite) ResetDB(c *check.C) {
|
||||
c.Fatal(err)
|
||||
}
|
||||
cfg := Config{
|
||||
IPPrefixes: []netaddr.IPPrefix{
|
||||
netaddr.MustParseIPPrefix("10.27.0.0/23"),
|
||||
IPPrefixes: []netip.Prefix{
|
||||
netip.MustParsePrefix("10.27.0.0/23"),
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -25,15 +25,18 @@ func init() {
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile == "" {
|
||||
cfgFile = os.Getenv("HEADSCALE_CONFIG")
|
||||
}
|
||||
if cfgFile != "" {
|
||||
err := headscale.LoadConfig(cfgFile, true)
|
||||
if err != nil {
|
||||
log.Fatal().Caller().Err(err)
|
||||
log.Fatal().Caller().Err(err).Msgf("Error loading config file %s", cfgFile)
|
||||
}
|
||||
} else {
|
||||
err := headscale.LoadConfig("", false)
|
||||
if err != nil {
|
||||
log.Fatal().Caller().Err(err)
|
||||
log.Fatal().Caller().Err(err).Msgf("Error loading config")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -163,10 +163,12 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
||||
c.Fatal(err)
|
||||
}
|
||||
// defer os.RemoveAll(tmpDir)
|
||||
|
||||
configYaml := []byte(
|
||||
"---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"",
|
||||
)
|
||||
configYaml := []byte(`---
|
||||
tls_letsencrypt_hostname: example.com
|
||||
tls_letsencrypt_challenge_type: ""
|
||||
tls_cert_path: abc.pem
|
||||
noise:
|
||||
private_key_path: noise_private.key`)
|
||||
writeConfig(c, tmpDir, configYaml)
|
||||
|
||||
// Check configuration validation errors (1)
|
||||
@@ -191,9 +193,13 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
||||
)
|
||||
|
||||
// Check configuration validation errors (2)
|
||||
configYaml = []byte(
|
||||
"---\nserver_url: \"http://127.0.0.1:8080\"\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"TLS-ALPN-01\"",
|
||||
)
|
||||
configYaml = []byte(`---
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
server_url: http://127.0.0.1:8080
|
||||
tls_letsencrypt_hostname: example.com
|
||||
tls_letsencrypt_challenge_type: TLS-ALPN-01
|
||||
`)
|
||||
writeConfig(c, tmpDir, configYaml)
|
||||
err = headscale.LoadConfig(tmpDir, false)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
@@ -41,6 +41,15 @@ grpc_allow_insecure: false
|
||||
# autogenerated if it's missing
|
||||
private_key_path: /var/lib/headscale/private.key
|
||||
|
||||
# The Noise section includes specific configuration for the
|
||||
# TS2021 Noise procotol
|
||||
noise:
|
||||
# The Noise private key is used to encrypt the
|
||||
# traffic between headscale and Tailscale clients when
|
||||
# using the new Noise-based protocol. It must be different
|
||||
# from the legacy private key.
|
||||
private_key_path: /var/lib/headscale/noise_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.
|
||||
|
32
config.go
32
config.go
@@ -9,11 +9,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"net/netip"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"inet.af/netaddr"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
)
|
||||
@@ -32,8 +34,9 @@ type Config struct {
|
||||
GRPCAllowInsecure bool
|
||||
EphemeralNodeInactivityTimeout time.Duration
|
||||
NodeUpdateCheckInterval time.Duration
|
||||
IPPrefixes []netaddr.IPPrefix
|
||||
IPPrefixes []netip.Prefix
|
||||
PrivateKeyPath string
|
||||
NoisePrivateKeyPath string
|
||||
BaseDomain string
|
||||
LogLevel zerolog.Level
|
||||
DisableUpdateCheck bool
|
||||
@@ -184,6 +187,10 @@ func LoadConfig(path string, isFile bool) error {
|
||||
errorText += "Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both\n"
|
||||
}
|
||||
|
||||
if !viper.IsSet("noise") || viper.GetString("noise.private_key_path") == "" {
|
||||
errorText += "Fatal config error: headscale now requires a new `noise.private_key_path` field in the config file for the Tailscale v2 protocol\n"
|
||||
}
|
||||
|
||||
if (viper.GetString("tls_letsencrypt_hostname") != "") &&
|
||||
(viper.GetString("tls_letsencrypt_challenge_type") == tlsALPN01ChallengeType) &&
|
||||
(!strings.HasSuffix(viper.GetString("listen_addr"), ":443")) {
|
||||
@@ -335,11 +342,11 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||
if viper.IsSet("dns_config.nameservers") {
|
||||
nameserversStr := viper.GetStringSlice("dns_config.nameservers")
|
||||
|
||||
nameservers := make([]netaddr.IP, len(nameserversStr))
|
||||
nameservers := make([]netip.Addr, len(nameserversStr))
|
||||
resolvers := make([]*dnstype.Resolver, len(nameserversStr))
|
||||
|
||||
for index, nameserverStr := range nameserversStr {
|
||||
nameserver, err := netaddr.ParseIP(nameserverStr)
|
||||
nameserver, err := netip.ParseAddr(nameserverStr)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("func", "getDNSConfig").
|
||||
@@ -369,7 +376,7 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||
len(restrictedNameservers),
|
||||
)
|
||||
for index, nameserverStr := range restrictedNameservers {
|
||||
nameserver, err := netaddr.ParseIP(nameserverStr)
|
||||
nameserver, err := netip.ParseAddr(nameserverStr)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("func", "getDNSConfig").
|
||||
@@ -422,7 +429,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||
randomizeClientPort := viper.GetBool("randomize_client_port")
|
||||
|
||||
configuredPrefixes := viper.GetStringSlice("ip_prefixes")
|
||||
parsedPrefixes := make([]netaddr.IPPrefix, 0, len(configuredPrefixes)+1)
|
||||
parsedPrefixes := make([]netip.Prefix, 0, len(configuredPrefixes)+1)
|
||||
|
||||
logLevelStr := viper.GetString("log_level")
|
||||
logLevel, err := zerolog.ParseLevel(logLevelStr)
|
||||
@@ -439,7 +446,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||
"use of 'ip_prefix' for configuration is deprecated",
|
||||
"please see 'ip_prefixes' in the shipped example.",
|
||||
)
|
||||
legacyPrefix, err := netaddr.ParseIPPrefix(legacyPrefixField)
|
||||
legacyPrefix, err := netip.ParsePrefix(legacyPrefixField)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse ip_prefix: %w", err))
|
||||
}
|
||||
@@ -447,19 +454,19 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||
}
|
||||
|
||||
for i, prefixInConfig := range configuredPrefixes {
|
||||
prefix, err := netaddr.ParseIPPrefix(prefixInConfig)
|
||||
prefix, err := netip.ParsePrefix(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))
|
||||
prefixes := make([]netip.Prefix, 0, len(parsedPrefixes))
|
||||
{
|
||||
// dedup
|
||||
normalizedPrefixes := make(map[string]int, len(parsedPrefixes))
|
||||
for i, p := range parsedPrefixes {
|
||||
normalized, _ := p.Range().Prefix()
|
||||
normalized, _ := netipx.RangeOfPrefix(p).Prefix()
|
||||
normalizedPrefixes[normalized.String()] = i
|
||||
}
|
||||
|
||||
@@ -470,7 +477,7 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||
}
|
||||
|
||||
if len(prefixes) < 1 {
|
||||
prefixes = append(prefixes, netaddr.MustParseIPPrefix("100.64.0.0/10"))
|
||||
prefixes = append(prefixes, netip.MustParsePrefix("100.64.0.0/10"))
|
||||
log.Warn().
|
||||
Msgf("'ip_prefixes' not configured, falling back to default: %v", prefixes)
|
||||
}
|
||||
@@ -488,6 +495,9 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||
PrivateKeyPath: AbsolutePathFromConfigPath(
|
||||
viper.GetString("private_key_path"),
|
||||
),
|
||||
NoisePrivateKeyPath: AbsolutePathFromConfigPath(
|
||||
viper.GetString("noise.private_key_path"),
|
||||
),
|
||||
BaseDomain: baseDomain,
|
||||
|
||||
DERP: derpConfig,
|
||||
|
4
db.go
4
db.go
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
@@ -259,7 +259,7 @@ func (hi HostInfo) Value() (driver.Value, error) {
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
type IPPrefixes []netaddr.IPPrefix
|
||||
type IPPrefixes []netip.Prefix
|
||||
|
||||
func (i *IPPrefixes) Scan(destination interface{}) error {
|
||||
switch value := destination.(type) {
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -163,7 +164,7 @@ func (h *Headscale) DERPHandler(
|
||||
pubKeyStr)
|
||||
}
|
||||
|
||||
h.DERPServer.tailscaleDERP.Accept(netConn, conn, netConn.RemoteAddr().String())
|
||||
h.DERPServer.tailscaleDERP.Accept(req.Context(), netConn, conn, netConn.RemoteAddr().String())
|
||||
}
|
||||
|
||||
// DERPProbeHandler is the endpoint that js/wasm clients hit to measure
|
||||
@@ -276,7 +277,8 @@ func serverSTUNListener(ctx context.Context, packetConn *net.UDPConn) {
|
||||
continue
|
||||
}
|
||||
|
||||
res := stun.Response(txid, udpAddr.IP, uint16(udpAddr.Port))
|
||||
addr, _ := netip.AddrFromSlice(udpAddr.IP)
|
||||
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(udpAddr.Port)))
|
||||
_, err = packetConn.WriteTo(res, udpAddr)
|
||||
if err != nil {
|
||||
log.Trace().Caller().Err(err).Msgf("Issue writing to UDP")
|
||||
|
21
dns.go
21
dns.go
@@ -2,10 +2,11 @@ package headscale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"inet.af/netaddr"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -39,11 +40,11 @@ const (
|
||||
|
||||
// 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.
|
||||
func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
|
||||
func generateMagicDNSRootDomains(ipPrefixes []netip.Prefix) []dnsname.FQDN {
|
||||
fqdns := make([]dnsname.FQDN, 0, len(ipPrefixes))
|
||||
for _, ipPrefix := range ipPrefixes {
|
||||
var generateDNSRoot func(netaddr.IPPrefix) []dnsname.FQDN
|
||||
switch ipPrefix.IP().BitLen() {
|
||||
var generateDNSRoot func(netip.Prefix) []dnsname.FQDN
|
||||
switch ipPrefix.Addr().BitLen() {
|
||||
case ipv4AddressLength:
|
||||
generateDNSRoot = generateIPv4DNSRootDomain
|
||||
|
||||
@@ -54,7 +55,7 @@ func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
|
||||
panic(
|
||||
fmt.Sprintf(
|
||||
"unsupported IP version with address length %d",
|
||||
ipPrefix.IP().BitLen(),
|
||||
ipPrefix.Addr().BitLen(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -65,9 +66,9 @@ func generateMagicDNSRootDomains(ipPrefixes []netaddr.IPPrefix) []dnsname.FQDN {
|
||||
return fqdns
|
||||
}
|
||||
|
||||
func generateIPv4DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
|
||||
func generateIPv4DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||
// Conversion to the std lib net.IPnet, a bit easier to operate
|
||||
netRange := ipPrefix.IPNet()
|
||||
netRange := netipx.PrefixIPNet(ipPrefix)
|
||||
maskBits, _ := netRange.Mask.Size()
|
||||
|
||||
// lastOctet is the last IP byte covered by the mask
|
||||
@@ -101,11 +102,11 @@ func generateIPv4DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
|
||||
return fqdns
|
||||
}
|
||||
|
||||
func generateIPv6DNSRootDomain(ipPrefix netaddr.IPPrefix) []dnsname.FQDN {
|
||||
func generateIPv6DNSRootDomain(ipPrefix netip.Prefix) []dnsname.FQDN {
|
||||
const nibbleLen = 4
|
||||
|
||||
maskBits, _ := ipPrefix.IPNet().Mask.Size()
|
||||
expanded := ipPrefix.IP().StringExpanded()
|
||||
maskBits, _ := netipx.PrefixIPNet(ipPrefix).Mask.Size()
|
||||
expanded := ipPrefix.Addr().StringExpanded()
|
||||
nibbleStr := strings.Map(func(r rune) rune {
|
||||
if r == ':' {
|
||||
return -1
|
||||
|
34
dns_test.go
34
dns_test.go
@@ -2,16 +2,16 @@ package headscale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
)
|
||||
|
||||
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
||||
prefixes := []netaddr.IPPrefix{
|
||||
netaddr.MustParseIPPrefix("100.64.0.0/10"),
|
||||
prefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.0.0/10"),
|
||||
}
|
||||
domains := generateMagicDNSRootDomains(prefixes)
|
||||
|
||||
@@ -47,8 +47,8 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
||||
prefixes := []netaddr.IPPrefix{
|
||||
netaddr.MustParseIPPrefix("172.16.0.0/16"),
|
||||
prefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("172.16.0.0/16"),
|
||||
}
|
||||
domains := generateMagicDNSRootDomains(prefixes)
|
||||
|
||||
@@ -75,8 +75,8 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
||||
|
||||
// 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"),
|
||||
prefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("fd7a:115c:a1e0::/48"),
|
||||
}
|
||||
domains := generateMagicDNSRootDomains(prefixes)
|
||||
|
||||
@@ -89,8 +89,8 @@ func (s *Suite) TestMagicDNSRootDomainsIPv6Single(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestMagicDNSRootDomainsIPv6SingleMultiple(c *check.C) {
|
||||
prefixes := []netaddr.IPPrefix{
|
||||
netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/50"),
|
||||
prefixes := []netip.Prefix{
|
||||
netip.MustParsePrefix("fd7a:115c:a1e0::/50"),
|
||||
}
|
||||
domains := generateMagicDNSRootDomains(prefixes)
|
||||
|
||||
@@ -165,7 +165,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
@@ -182,7 +182,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared2.ID,
|
||||
Namespace: *namespaceShared2,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
@@ -199,7 +199,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared3.ID,
|
||||
Namespace: *namespaceShared3,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
@@ -216,7 +216,7 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
@@ -308,7 +308,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
@@ -325,7 +325,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared2.ID,
|
||||
Namespace: *namespaceShared2,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
@@ -342,7 +342,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared3.ID,
|
||||
Namespace: *namespaceShared3,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
@@ -359,7 +359,7 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
|
@@ -54,6 +54,9 @@ metrics_listen_addr: 0.0.0.0:9090
|
||||
# The default /var/lib/headscale path is not writable in the container
|
||||
private_key_path: /etc/headscale/private.key
|
||||
# The default /var/lib/headscale path is not writable in the container
|
||||
noise:
|
||||
private_key_path: /var/lib/headscale/noise_private.key
|
||||
# The default /var/lib/headscale path is not writable in the container
|
||||
db_path: /etc/headscale/db.sqlite
|
||||
```
|
||||
|
||||
|
@@ -17,7 +17,7 @@ describing how to make `headscale` run properly in a server environment.
|
||||
|
||||
```shell
|
||||
# Install prerequistes
|
||||
# 1. go v1.18+: headscale newer than 0.15 needs go 1.18+ to compile
|
||||
# 1. go v1.19+: headscale newer than 0.17 needs go 1.19+ to compile
|
||||
# 2. gmake: Makefile in the headscale repo is written in GNU make syntax
|
||||
pkg_add -D snap go
|
||||
pkg_add gmake
|
||||
@@ -46,7 +46,7 @@ cp headscale /usr/local/sbin
|
||||
|
||||
```shell
|
||||
# Install prerequistes
|
||||
# 1. go v1.18+: headscale newer than 0.15 needs go 1.18+ to compile
|
||||
# 1. go v1.19+: headscale newer than 0.17 needs go 1.19+ to compile
|
||||
# 2. gmake: Makefile in the headscale repo is written in GNU make syntax
|
||||
|
||||
git clone https://github.com/juanfont/headscale.git
|
||||
|
@@ -24,7 +24,7 @@
|
||||
|
||||
# When updating go.mod or go.sum, a new sha will need to be calculated,
|
||||
# update this if you have a mismatch after doing a change to thos files.
|
||||
vendorSha256 = "sha256-RzmnAh81BN4tbzAGzJbb6CMuws8kuPJDw7aPkRRnSS8=";
|
||||
vendorSha256 = "sha256-paDdPsi5OfxsmgX7c5NSDSLYDipFqxxcxV3K4Tc77nQ=";
|
||||
|
||||
ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ];
|
||||
};
|
||||
|
19
go.mod
19
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/juanfont/headscale
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
@@ -14,6 +14,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282
|
||||
github.com/ory/dockertest/v3 v3.9.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||
@@ -27,7 +28,9 @@ require (
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
|
||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
google.golang.org/genproto v0.0.0-20220808204814-fd01256a5276
|
||||
@@ -39,12 +42,13 @@ require (
|
||||
gorm.io/driver/postgres v1.3.8
|
||||
gorm.io/gorm v1.23.8
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321
|
||||
tailscale.com v1.28.0
|
||||
tailscale.com v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.1.1 // indirect
|
||||
atomicgo.dev/keyboard v0.2.8 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
@@ -61,8 +65,11 @@ require (
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.17.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||
@@ -72,6 +79,7 @@ require (
|
||||
github.com/gookit/color v1.5.0 // indirect
|
||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
@@ -119,6 +127,7 @@ require (
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.3.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
@@ -126,17 +135,17 @@ require (
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.4.10 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
modernc.org/libc v1.16.8 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
modernc.org/sqlite v1.17.3 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
69
go.sum
69
go.sum
@@ -56,6 +56,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/mkcert v1.4.3 h1:axpnmtrZMM8u5Hf4N3UXxboGemMOV+Tn+e+pkHM6E3o=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo=
|
||||
@@ -64,6 +67,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
@@ -234,8 +238,14 @@ github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/glebarez/go-sqlite v1.17.3 h1:Rji9ROVSTTfjuWD6j5B+8DtkNvPILoUC3xRhkQzGxvk=
|
||||
github.com/glebarez/go-sqlite v1.17.3/go.mod h1:Hg+PQuhUy98XCxWEJEaWob8x7lhJzhNYF1nZbUiRGIY=
|
||||
github.com/glebarez/sqlite v1.4.6 h1:D5uxD2f6UJ82cHnVtO2TZ9pqsLyto3fpDKHIk2OsR8A=
|
||||
@@ -254,6 +264,13 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
@@ -273,6 +290,12 @@ github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2
|
||||
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
@@ -285,12 +308,16 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@@ -398,6 +425,7 @@ github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b0
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
@@ -456,6 +484,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU=
|
||||
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -538,8 +568,10 @@ github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b h1:Yws7RV6k
|
||||
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b/go.mod h1:TzDCVOZKUa79z6iXbbXqhtAflVgUKaFkZ21M5tK5tzY=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
@@ -556,6 +588,7 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
@@ -585,6 +618,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
|
||||
github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
|
||||
github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0=
|
||||
github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -675,9 +710,11 @@ github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdx
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
|
||||
@@ -696,6 +733,8 @@ github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62
|
||||
github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 h1:TQMyrpijtkFyXpNI3rY5hsZQZw+paiH+BfAlsb81HBY=
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282/go.mod h1:rW25Kyd08Wdn3UVn0YBsDTSvReu0jqpmJKzxITPSjks=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
@@ -918,7 +957,11 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@@ -931,6 +974,8 @@ github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7Fw
|
||||
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
@@ -986,6 +1031,8 @@ go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3m
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4=
|
||||
go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf h1:IdwJUzqoIo5lkr2EOyKoe5qipUaEjbOKKY5+fzPBZ3A=
|
||||
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf/go.mod h1:+QXzaoURFd0rGDIjDNpyIkv+F9R7EmeKorvlKRnhqgA=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
@@ -1011,6 +1058,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1024,6 +1072,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb h1:fP6C8Xutcp5AlakmT/SkQot0pMicROAsEX7OfNPuG10=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -1050,6 +1100,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1104,6 +1155,7 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
@@ -1240,8 +1292,8 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -1374,6 +1426,7 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1548,8 +1601,9 @@ gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
@@ -1582,6 +1636,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83 h1:lZ9GIYaU+o5+X6ST702I/Ntyq9Y2oIMZ42rBQpem64A=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU=
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
@@ -1614,9 +1670,12 @@ mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
tailscale.com v1.28.0 h1:eW5bJMqw6eu7YUjBcgJY94uIcm5Zv+xpyTxxa7ztZOM=
|
||||
tailscale.com v1.28.0/go.mod h1:T9uKhlkxVPdSu1Qvp882evcS/hQ1+TAyZ7sJ/VACGRI=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 h1:SqYE5+A2qvRhErbsXFfUEUmpWEKxxRSMgGLkvRAFOV4=
|
||||
tailscale.com v1.30.0 h1:J8k19aVG5z2W7FhpjkJyZ53HKb0tiNR1icvWly36Pvg=
|
||||
tailscale.com v1.30.0/go.mod h1:MO+tWkQp2YIF3KBnnej/mQvgYccRS5Xk/IrEpZ4Z3BU=
|
||||
|
@@ -1739,6 +1739,8 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
|
||||
assert.Nil(s.T(), err)
|
||||
altConfig, err := os.ReadFile("integration_test/etc/alt-config.dump.gold.yaml")
|
||||
assert.Nil(s.T(), err)
|
||||
altEnvConfig, err := os.ReadFile("integration_test/etc/alt-env-config.dump.gold.yaml")
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
_, err = ExecuteCommand(
|
||||
&s.headscale,
|
||||
@@ -1771,4 +1773,40 @@ func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() {
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig))
|
||||
|
||||
_, err = ExecuteCommand(
|
||||
&s.headscale,
|
||||
[]string{
|
||||
"headscale",
|
||||
"dumpConfig",
|
||||
},
|
||||
[]string{
|
||||
"HEADSCALE_CONFIG=/etc/headscale/alt-env-config.yaml",
|
||||
},
|
||||
)
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
altEnvDumpConfig, err := os.ReadFile("integration_test/etc/config.dump.yaml")
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
assert.YAMLEq(s.T(), string(altEnvConfig), string(altEnvDumpConfig))
|
||||
|
||||
_, err = ExecuteCommand(
|
||||
&s.headscale,
|
||||
[]string{
|
||||
"headscale",
|
||||
"-c",
|
||||
"/etc/headscale/alt-config.yaml",
|
||||
"dumpConfig",
|
||||
},
|
||||
[]string{
|
||||
"HEADSCALE_CONFIG=/etc/headscale/alt-env-config.yaml",
|
||||
},
|
||||
)
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
altDumpConfig, err = os.ReadFile("integration_test/etc/config.dump.yaml")
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig))
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,12 +26,13 @@ const (
|
||||
var (
|
||||
errEnvVarEmpty = errors.New("getenv: environment variable empty")
|
||||
|
||||
IpPrefix4 = netaddr.MustParseIPPrefix("100.64.0.0/10")
|
||||
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
||||
IpPrefix4 = netip.MustParsePrefix("100.64.0.0/10")
|
||||
IpPrefix6 = netip.MustParsePrefix("fd7a:115c:a1e0::/48")
|
||||
|
||||
tailscaleVersions = []string{
|
||||
// "head",
|
||||
// "unstable",
|
||||
"1.30.0",
|
||||
"1.28.0",
|
||||
"1.26.2",
|
||||
"1.24.2",
|
||||
@@ -194,8 +195,8 @@ func getDockerBuildOptions(version string) *dockertest.BuildOptions {
|
||||
|
||||
func getIPs(
|
||||
tailscales map[string]dockertest.Resource,
|
||||
) (map[string][]netaddr.IP, error) {
|
||||
ips := make(map[string][]netaddr.IP)
|
||||
) (map[string][]netip.Addr, error) {
|
||||
ips := make(map[string][]netip.Addr)
|
||||
for hostname, tailscale := range tailscales {
|
||||
command := []string{"tailscale", "ip"}
|
||||
|
||||
@@ -213,7 +214,7 @@ func getIPs(
|
||||
if len(address) < 1 {
|
||||
continue
|
||||
}
|
||||
ip, err := netaddr.ParseIP(address)
|
||||
ip, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -22,7 +23,6 @@ import (
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
)
|
||||
@@ -477,8 +477,8 @@ func (s *IntegrationTestSuite) TestGetIpAddresses() {
|
||||
// }
|
||||
// }
|
||||
|
||||
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
||||
ips := make([]netaddr.IP, 0)
|
||||
func getIPsfromIPNstate(status ipnstate.Status) []netip.Addr {
|
||||
ips := make([]netip.Addr, 0)
|
||||
|
||||
for _, peer := range status.Peer {
|
||||
ips = append(ips, peer.TailscaleIPs...)
|
||||
@@ -562,13 +562,25 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
||||
if peername == hostname {
|
||||
continue
|
||||
}
|
||||
|
||||
var ip4 netip.Addr
|
||||
for _, ip := range ips[peername] {
|
||||
if ip.Is4() {
|
||||
ip4 = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
if ip4.IsUnspecified() {
|
||||
panic("no ipv4 address found")
|
||||
}
|
||||
|
||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||
command := []string{
|
||||
"tailscale", "file", "cp",
|
||||
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
||||
fmt.Sprintf("%s:", ips[peername][1]),
|
||||
fmt.Sprintf("%s:", ip4),
|
||||
}
|
||||
retry(10, 1*time.Second, func() error {
|
||||
err := retry(10, 1*time.Second, func() error {
|
||||
log.Printf(
|
||||
"Sending file from %s to %s\n",
|
||||
hostname,
|
||||
@@ -582,6 +594,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
||||
)
|
||||
return err
|
||||
})
|
||||
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
@@ -683,6 +696,18 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
||||
ips, err := getIPs(scales.tailscales)
|
||||
assert.Nil(s.T(), err)
|
||||
|
||||
retry := func(times int, sleepInverval time.Duration, doWork func() (string, error)) (result string, err error) {
|
||||
for attempts := 0; attempts < times; attempts++ {
|
||||
result, err = doWork()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(sleepInverval)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for hostname, tailscale := range scales.tailscales {
|
||||
for _, peername := range hostnames {
|
||||
if strings.Contains(peername, hostname) {
|
||||
@@ -693,7 +718,7 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
||||
command := []string{
|
||||
"tailscale", "ip", peername,
|
||||
}
|
||||
|
||||
result, err := retry(10, 1*time.Second, func() (string, error) {
|
||||
log.Printf(
|
||||
"Resolving name %s from %s\n",
|
||||
peername,
|
||||
@@ -704,6 +729,9 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
||||
command,
|
||||
[]string{},
|
||||
)
|
||||
return result, err
|
||||
})
|
||||
|
||||
assert.Nil(t, err)
|
||||
log.Printf("Result for %s: %s\n", hostname, result)
|
||||
|
||||
@@ -720,8 +748,8 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
||||
|
||||
func getAPIURLs(
|
||||
tailscales map[string]dockertest.Resource,
|
||||
) (map[netaddr.IP]string, error) {
|
||||
fts := make(map[netaddr.IP]string)
|
||||
) (map[netip.Addr]string, error) {
|
||||
fts := make(map[netip.Addr]string)
|
||||
for _, tailscale := range tailscales {
|
||||
command := []string{
|
||||
"curl",
|
||||
@@ -745,11 +773,11 @@ func getAPIURLs(
|
||||
for _, ft := range pft {
|
||||
n := ft.Node
|
||||
for _, a := range n.Addresses { // just add all the addresses
|
||||
if _, ok := fts[a.IP()]; !ok {
|
||||
if _, ok := fts[a.Addr()]; !ok {
|
||||
if ft.PeerAPIURL == "" {
|
||||
return nil, errors.New("api url is empty")
|
||||
}
|
||||
fts[a.IP()] = ft.PeerAPIURL
|
||||
fts[a.Addr()] = ft.PeerAPIURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ dns_config:
|
||||
domains: []
|
||||
magic_dns: true
|
||||
nameservers:
|
||||
- 127.0.0.11
|
||||
- 1.1.1.1
|
||||
ephemeral_node_inactivity_timeout: 30m
|
||||
node_update_check_interval: 10s
|
||||
@@ -38,6 +39,8 @@ oidc:
|
||||
- email
|
||||
strip_email_domain: true
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
server_url: http://headscale:18080
|
||||
tls_client_auth_mode: relaxed
|
||||
tls_letsencrypt_cache_dir: /var/www/.cache
|
||||
|
@@ -11,9 +11,12 @@ dns_config:
|
||||
magic_dns: true
|
||||
domains: []
|
||||
nameservers:
|
||||
- 127.0.0.11
|
||||
- 1.1.1.1
|
||||
db_path: /tmp/integration_test_db.sqlite3
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
listen_addr: 0.0.0.0:18080
|
||||
metrics_listen_addr: 127.0.0.1:19090
|
||||
server_url: http://headscale:18080
|
||||
|
49
integration_test/etc/alt-env-config.dump.gold.yaml
Normal file
49
integration_test/etc/alt-env-config.dump.gold.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
acl_policy_path: ""
|
||||
cli:
|
||||
insecure: false
|
||||
timeout: 5s
|
||||
db_path: /tmp/integration_test_db.sqlite3
|
||||
db_type: sqlite3
|
||||
derp:
|
||||
auto_update_enabled: false
|
||||
server:
|
||||
enabled: false
|
||||
stun:
|
||||
enabled: true
|
||||
update_frequency: 1m
|
||||
urls:
|
||||
- https://controlplane.tailscale.com/derpmap/default
|
||||
dns_config:
|
||||
base_domain: headscale.net
|
||||
domains: []
|
||||
magic_dns: true
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
ephemeral_node_inactivity_timeout: 30m
|
||||
node_update_check_interval: 30s
|
||||
grpc_allow_insecure: false
|
||||
grpc_listen_addr: :50443
|
||||
ip_prefixes:
|
||||
- fd7a:115c:a1e0::/48
|
||||
- 100.64.0.0/10
|
||||
listen_addr: 0.0.0.0:18080
|
||||
log_level: disabled
|
||||
logtail:
|
||||
enabled: false
|
||||
metrics_listen_addr: 127.0.0.1:19090
|
||||
oidc:
|
||||
scope:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
strip_email_domain: true
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
server_url: http://headscale:18080
|
||||
tls_client_auth_mode: relaxed
|
||||
tls_letsencrypt_cache_dir: /var/www/.cache
|
||||
tls_letsencrypt_challenge_type: HTTP-01
|
||||
unix_socket: /var/run/headscale.sock
|
||||
unix_socket_permission: "0o770"
|
||||
randomize_client_port: false
|
27
integration_test/etc/alt-env-config.yaml
Normal file
27
integration_test/etc/alt-env-config.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
log_level: trace
|
||||
acl_policy_path: ""
|
||||
db_type: sqlite3
|
||||
ephemeral_node_inactivity_timeout: 30m
|
||||
node_update_check_interval: 30s
|
||||
ip_prefixes:
|
||||
- fd7a:115c:a1e0::/48
|
||||
- 100.64.0.0/10
|
||||
dns_config:
|
||||
base_domain: headscale.net
|
||||
magic_dns: true
|
||||
domains: []
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
db_path: /tmp/integration_test_db.sqlite3
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
listen_addr: 0.0.0.0:18080
|
||||
metrics_listen_addr: 127.0.0.1:19090
|
||||
server_url: http://headscale:18080
|
||||
|
||||
derp:
|
||||
urls:
|
||||
- https://controlplane.tailscale.com/derpmap/default
|
||||
auto_update_enabled: false
|
||||
update_frequency: 1m
|
@@ -18,6 +18,7 @@ dns_config:
|
||||
domains: []
|
||||
magic_dns: true
|
||||
nameservers:
|
||||
- 127.0.0.11
|
||||
- 1.1.1.1
|
||||
ephemeral_node_inactivity_timeout: 30m
|
||||
node_update_check_interval: 10s
|
||||
@@ -38,6 +39,8 @@ oidc:
|
||||
- email
|
||||
strip_email_domain: true
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
server_url: http://headscale:8080
|
||||
tls_client_auth_mode: relaxed
|
||||
tls_letsencrypt_cache_dir: /var/www/.cache
|
||||
|
@@ -11,9 +11,12 @@ dns_config:
|
||||
magic_dns: true
|
||||
domains: []
|
||||
nameservers:
|
||||
- 127.0.0.11
|
||||
- 1.1.1.1
|
||||
db_path: /tmp/integration_test_db.sqlite3
|
||||
private_key_path: private.key
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
listen_addr: 0.0.0.0:8080
|
||||
metrics_listen_addr: 127.0.0.1:9090
|
||||
server_url: http://headscale:8080
|
||||
|
@@ -14,8 +14,10 @@ dns_config:
|
||||
- 1.1.1.1
|
||||
db_path: /tmp/integration_test_db.sqlite3
|
||||
private_key_path: private.key
|
||||
listen_addr: 0.0.0.0:8443
|
||||
server_url: https://headscale:8443
|
||||
noise:
|
||||
private_key_path: noise_private.key
|
||||
listen_addr: 0.0.0.0:443
|
||||
server_url: https://headscale:443
|
||||
tls_cert_path: "/etc/headscale/tls/server.crt"
|
||||
tls_key_path: "/etc/headscale/tls/server.key"
|
||||
tls_client_auth_mode: disabled
|
||||
|
50
machine.go
50
machine.go
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
@@ -83,7 +83,7 @@ type (
|
||||
MachinesP []*Machine
|
||||
)
|
||||
|
||||
type MachineAddresses []netaddr.IP
|
||||
type MachineAddresses []netip.Addr
|
||||
|
||||
func (ma MachineAddresses) ToStringSlice() []string {
|
||||
strSlice := make([]string, 0, len(ma))
|
||||
@@ -103,7 +103,7 @@ func (ma *MachineAddresses) Scan(destination interface{}) error {
|
||||
if len(addr) < 1 {
|
||||
continue
|
||||
}
|
||||
parsed, err := netaddr.ParseIP(addr)
|
||||
parsed, err := netip.ParseAddr(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -245,8 +245,8 @@ func (h *Headscale) ListPeers(machine *Machine) (Machines, error) {
|
||||
Msg("Finding direct peers")
|
||||
|
||||
machines := Machines{}
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("machine_key <> ?",
|
||||
machine.MachineKey).Find(&machines).Error; err != nil {
|
||||
if err := h.db.Preload("AuthKey").Preload("AuthKey.Namespace").Preload("Namespace").Where("node_key <> ?",
|
||||
machine.NodeKey).Find(&machines).Error; err != nil {
|
||||
log.Error().Err(err).Msg("Error accessing db")
|
||||
|
||||
return Machines{}, err
|
||||
@@ -376,6 +376,19 @@ func (h *Headscale) GetMachineByNodeKey(
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
// GetMachineByAnyNodeKey finds a Machine by its current NodeKey or the old one, and returns the Machine struct.
|
||||
func (h *Headscale) GetMachineByAnyNodeKey(
|
||||
nodeKey key.NodePublic, oldNodeKey key.NodePublic,
|
||||
) (*Machine, error) {
|
||||
machine := Machine{}
|
||||
if result := h.db.Preload("Namespace").First(&machine, "node_key = ? OR node_key = ?",
|
||||
NodePublicKeyStripPrefix(nodeKey), NodePublicKeyStripPrefix(oldNodeKey)); result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
// UpdateMachineFromDatabase takes a Machine struct pointer (typically already loaded from database
|
||||
// and updates it with the latest data from the database.
|
||||
func (h *Headscale) UpdateMachineFromDatabase(machine *Machine) error {
|
||||
@@ -398,7 +411,7 @@ func (h *Headscale) SetTags(machine *Machine, tags []string) error {
|
||||
if err := h.UpdateACLRules(); err != nil && !errors.Is(err, errEmptyPolicy) {
|
||||
return err
|
||||
}
|
||||
h.setLastStateChangeToNow(machine.Namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to update tags for machine in the database: %w", err)
|
||||
@@ -412,7 +425,7 @@ func (h *Headscale) ExpireMachine(machine *Machine) error {
|
||||
now := time.Now()
|
||||
machine.Expiry = &now
|
||||
|
||||
h.setLastStateChangeToNow(machine.Namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to expire machine in the database: %w", err)
|
||||
@@ -439,7 +452,7 @@ func (h *Headscale) RenameMachine(machine *Machine, newName string) error {
|
||||
}
|
||||
machine.GivenName = newName
|
||||
|
||||
h.setLastStateChangeToNow(machine.Namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf("failed to rename machine in the database: %w", err)
|
||||
@@ -455,7 +468,7 @@ func (h *Headscale) RefreshMachine(machine *Machine, expiry time.Time) error {
|
||||
machine.LastSuccessfulUpdate = &now
|
||||
machine.Expiry = &expiry
|
||||
|
||||
h.setLastStateChangeToNow(machine.Namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
if err := h.db.Save(machine).Error; err != nil {
|
||||
return fmt.Errorf(
|
||||
@@ -588,12 +601,15 @@ func (machine Machine) toNode(
|
||||
}
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
// MachineKey is only used in the legacy protocol
|
||||
if machine.MachineKey != "" {
|
||||
err = machineKey.UnmarshalText(
|
||||
[]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse machine public key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var discoKey key.DiscoPublic
|
||||
if machine.DiscoKey != "" {
|
||||
@@ -607,14 +623,14 @@ func (machine Machine) toNode(
|
||||
discoKey = key.DiscoPublic{}
|
||||
}
|
||||
|
||||
addrs := []netaddr.IPPrefix{}
|
||||
addrs := []netip.Prefix{}
|
||||
for _, machineAddress := range machine.IPAddresses {
|
||||
ip := netaddr.IPPrefixFrom(machineAddress, machineAddress.BitLen())
|
||||
ip := netip.PrefixFrom(machineAddress, machineAddress.BitLen())
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
|
||||
allowedIPs := append(
|
||||
[]netaddr.IPPrefix{},
|
||||
[]netip.Prefix{},
|
||||
addrs...) // we append the node own IP, as it is required by the clients
|
||||
|
||||
// TODO(kradalby): Needs investigation, We probably dont need this condition
|
||||
@@ -857,16 +873,16 @@ func (h *Headscale) RegisterMachine(machine Machine,
|
||||
return &machine, nil
|
||||
}
|
||||
|
||||
func (machine *Machine) GetAdvertisedRoutes() []netaddr.IPPrefix {
|
||||
func (machine *Machine) GetAdvertisedRoutes() []netip.Prefix {
|
||||
return machine.HostInfo.RoutableIPs
|
||||
}
|
||||
|
||||
func (machine *Machine) GetEnabledRoutes() []netaddr.IPPrefix {
|
||||
func (machine *Machine) GetEnabledRoutes() []netip.Prefix {
|
||||
return machine.EnabledRoutes
|
||||
}
|
||||
|
||||
func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
|
||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -885,9 +901,9 @@ func (machine *Machine) IsRoutesEnabled(routeStr string) bool {
|
||||
// EnableNodeRoute enables new routes based on a list of new routes. It will _replace_ the
|
||||
// previous list of routes.
|
||||
func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
|
||||
newRoutes := make([]netaddr.IPPrefix, len(routeStrs))
|
||||
newRoutes := make([]netip.Prefix, len(routeStrs))
|
||||
for index, routeStr := range routeStrs {
|
||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
144
machine_test.go
144
machine_test.go
@@ -2,6 +2,7 @@ package headscale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -9,8 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func (s *Suite) TestGetMachine(c *check.C) {
|
||||
@@ -65,6 +66,63 @@ func (s *Suite) TestGetMachineByID(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByNodeKey(c *check.C) {
|
||||
namespace, err := app.CreateNamespace("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
|
||||
_, err = app.GetMachineByNodeKey(nodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestGetMachineByAnyNodeKey(c *check.C) {
|
||||
namespace, err := app.CreateNamespace("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
_, err = app.GetMachineByID(0)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
nodeKey := key.NewNode()
|
||||
oldNodeKey := key.NewNode()
|
||||
|
||||
machine := Machine{
|
||||
ID: 0,
|
||||
MachineKey: "foo",
|
||||
NodeKey: NodePublicKeyStripPrefix(nodeKey.Public()),
|
||||
DiscoKey: "faa",
|
||||
Hostname: "testmachine",
|
||||
NamespaceID: namespace.ID,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
app.db.Save(&machine)
|
||||
|
||||
_, err = app.GetMachineByAnyNodeKey(nodeKey.Public(), oldNodeKey.Public())
|
||||
c.Assert(err, check.IsNil)
|
||||
}
|
||||
|
||||
func (s *Suite) TestDeleteMachine(c *check.C) {
|
||||
namespace, err := app.CreateNamespace("test")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -171,7 +229,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
NodeKey: "bar" + strconv.Itoa(index),
|
||||
DiscoKey: "faa" + strconv.Itoa(index),
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
|
||||
netip.MustParseAddr(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
|
||||
},
|
||||
Hostname: "testmachine" + strconv.Itoa(index),
|
||||
NamespaceID: stor[index%2].namespace.ID,
|
||||
@@ -185,7 +243,7 @@ func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
|
||||
Groups: map[string][]string{
|
||||
"group:test": {"admin"},
|
||||
},
|
||||
Hosts: map[string]netaddr.IPPrefix{},
|
||||
Hosts: map[string]netip.Prefix{},
|
||||
TagOwners: map[string][]string{},
|
||||
ACLs: []ACL{
|
||||
{
|
||||
@@ -268,9 +326,9 @@ func (s *Suite) TestExpireMachine(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
|
||||
input := MachineAddresses([]netaddr.IP{
|
||||
netaddr.MustParseIP("192.0.2.1"),
|
||||
netaddr.MustParseIP("2001:db8::1"),
|
||||
input := MachineAddresses([]netip.Addr{
|
||||
netip.MustParseAddr("192.0.2.1"),
|
||||
netip.MustParseAddr("2001:db8::1"),
|
||||
})
|
||||
serialized, err := input.Value()
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -501,21 +559,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -530,19 +588,19 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
},
|
||||
machine: &Machine{ // current machine
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
},
|
||||
want: Machines{
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
@@ -554,21 +612,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -583,14 +641,14 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
},
|
||||
machine: &Machine{ // current machine
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.1")},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
},
|
||||
want: Machines{
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
},
|
||||
@@ -602,21 +660,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -631,14 +689,14 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
},
|
||||
machine: &Machine{ // current machine
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
},
|
||||
want: Machines{
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
@@ -650,21 +708,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -680,7 +738,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
machine: &Machine{ // current machine
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
@@ -689,7 +747,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
@@ -702,21 +760,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -732,7 +790,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
machine: &Machine{ // current machine
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
@@ -741,14 +799,14 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -761,21 +819,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -790,7 +848,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
},
|
||||
machine: &Machine{ // current machine
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
},
|
||||
@@ -798,13 +856,13 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.3")},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
},
|
||||
@@ -816,21 +874,21 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.1"),
|
||||
netip.MustParseAddr("100.64.0.1"),
|
||||
},
|
||||
Namespace: Namespace{Name: "joe"},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.2"),
|
||||
netip.MustParseAddr("100.64.0.2"),
|
||||
},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
IPAddresses: MachineAddresses{
|
||||
netaddr.MustParseIP("100.64.0.3"),
|
||||
netip.MustParseAddr("100.64.0.3"),
|
||||
},
|
||||
Namespace: Namespace{Name: "mickael"},
|
||||
},
|
||||
@@ -839,7 +897,7 @@ func Test_getFilteredByACLPeers(t *testing.T) {
|
||||
},
|
||||
machine: &Machine{ // current machine
|
||||
ID: 2,
|
||||
IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: MachineAddresses{netip.MustParseAddr("100.64.0.2")},
|
||||
Namespace: Namespace{Name: "marc"},
|
||||
},
|
||||
},
|
||||
|
@@ -1,11 +1,11 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"gorm.io/gorm"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
|
||||
@@ -146,7 +146,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.1")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.1")},
|
||||
AuthKeyID: uint(preAuthKeyShared1.ID),
|
||||
}
|
||||
app.db.Save(machineInShared1)
|
||||
@@ -163,7 +163,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
NamespaceID: namespaceShared2.ID,
|
||||
Namespace: *namespaceShared2,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.2")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.2")},
|
||||
AuthKeyID: uint(preAuthKeyShared2.ID),
|
||||
}
|
||||
app.db.Save(machineInShared2)
|
||||
@@ -180,7 +180,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
NamespaceID: namespaceShared3.ID,
|
||||
Namespace: *namespaceShared3,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.3")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.3")},
|
||||
AuthKeyID: uint(preAuthKeyShared3.ID),
|
||||
}
|
||||
app.db.Save(machineInShared3)
|
||||
@@ -197,7 +197,7 @@ func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||
NamespaceID: namespaceShared1.ID,
|
||||
Namespace: *namespaceShared1,
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
IPAddresses: []netaddr.IP{netaddr.MustParseIP("100.64.0.4")},
|
||||
IPAddresses: []netip.Addr{netip.MustParseAddr("100.64.0.4")},
|
||||
AuthKeyID: uint(preAuthKey2Shared1.ID),
|
||||
}
|
||||
app.db.Save(machine2InShared1)
|
||||
|
40
noise.go
Normal file
40
noise.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/net/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// ts2021UpgradePath is the path that the server listens on for the WebSockets upgrade.
|
||||
ts2021UpgradePath = "/ts2021"
|
||||
)
|
||||
|
||||
// NoiseUpgradeHandler is to upgrade the connection and hijack the net.Conn
|
||||
// in order to use the Noise-based TS2021 protocol. Listens in /ts2021.
|
||||
func (h *Headscale) NoiseUpgradeHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
log.Trace().Caller().Msgf("Noise upgrade handler for client %s", req.RemoteAddr)
|
||||
|
||||
noiseConn, err := controlhttp.AcceptHTTP(req.Context(), writer, req, *h.noisePrivateKey)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("noise upgrade failed")
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
server := http.Server{}
|
||||
server.Handler = h2c.NewHandler(h.noiseMux, &http2.Server{})
|
||||
err = server.Serve(netutil.NewOneConnListener(noiseConn, nil))
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("The HTTP2 server was closed")
|
||||
}
|
||||
}
|
749
protocol_common.go
Normal file
749
protocol_common.go
Normal file
@@ -0,0 +1,749 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
// The CapabilityVersion is used by Tailscale clients to indicate
|
||||
// their codebase version. Tailscale clients can communicate over TS2021
|
||||
// from CapabilityVersion 28, but we only have good support for it
|
||||
// since https://github.com/tailscale/tailscale/pull/4323 (Noise in any HTTPS port).
|
||||
//
|
||||
// Related to this change, there is https://github.com/tailscale/tailscale/pull/5379,
|
||||
// where CapabilityVersion 39 is introduced to indicate #4323 was merged.
|
||||
//
|
||||
// See also https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go
|
||||
NoiseCapabilityVersion = 39
|
||||
)
|
||||
|
||||
// KeyHandler provides the Headscale pub key
|
||||
// Listens in /key.
|
||||
func (h *Headscale) KeyHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
// New Tailscale clients send a 'v' parameter to indicate the CurrentCapabilityVersion
|
||||
clientCapabilityStr := req.URL.Query().Get("v")
|
||||
if clientCapabilityStr != "" {
|
||||
log.Debug().
|
||||
Str("handler", "/key").
|
||||
Str("v", clientCapabilityStr).
|
||||
Msg("New noise client")
|
||||
clientCapabilityVersion, err := strconv.Atoi(clientCapabilityStr)
|
||||
if err != nil {
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
_, err := writer.Write([]byte("Wrong params"))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TS2021 (Tailscale v2 protocol) requires to have a different key
|
||||
if clientCapabilityVersion >= NoiseCapabilityVersion {
|
||||
resp := tailcfg.OverTLSPublicKeyResponse{
|
||||
LegacyPublicKey: h.privateKey.Public(),
|
||||
PublicKey: h.noisePrivateKey.Public(),
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(writer).Encode(resp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Debug().
|
||||
Str("handler", "/key").
|
||||
Msg("New legacy client")
|
||||
|
||||
// Old clients don't send a 'v' parameter, so we send the legacy public key
|
||||
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err := writer.Write([]byte(MachinePublicKeyStripPrefix(h.privateKey.Public())))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
}
|
||||
|
||||
// handleRegisterCommon is the common logic for registering a client in the legacy and Noise protocols
|
||||
//
|
||||
// When using Noise, the machineKey is Zero.
|
||||
func (h *Headscale) handleRegisterCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
now := time.Now().UTC()
|
||||
machine, err := h.GetMachineByAnyNodeKey(registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
h.handleAuthKeyCommon(writer, req, registerRequest, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the node is waiting for interactive login.
|
||||
//
|
||||
// TODO(juan): We could use this field to improve our protocol implementation,
|
||||
// and hold the request until the client closes it, or the interactive
|
||||
// login is completed (i.e., the user registers the machine).
|
||||
// This is not implemented yet, as it is no strictly required. The only side-effect
|
||||
// is that the client will hammer headscale with requests until it gets a
|
||||
// successful RegisterResponse.
|
||||
if registerRequest.Followup != "" {
|
||||
if _, ok := h.registrationCache.Get(NodePublicKeyStripPrefix(registerRequest.NodeKey)); ok {
|
||||
log.Debug().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Msg("Machine is waiting for interactive login")
|
||||
|
||||
ticker := time.NewTicker(registrationHoldoff)
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
h.handleNewMachineCommon(writer, req, registerRequest, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("node_key_old", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("follow_up", registerRequest.Followup).
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Msg("New machine not yet in the database")
|
||||
|
||||
givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "RegistrationHandler").
|
||||
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
|
||||
Err(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine did not have a key to authenticate, which means
|
||||
// that we rely on a method that calls back some how (OpenID or CLI)
|
||||
// We create the machine and then keep it around until a callback
|
||||
// happens
|
||||
newMachine := Machine{
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
NodeKey: NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
LastSeen: &now,
|
||||
Expiry: &time.Time{},
|
||||
}
|
||||
|
||||
if !registerRequest.Expiry.IsZero() {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Time("expiry", registerRequest.Expiry).
|
||||
Msg("Non-zero expiry time requested")
|
||||
newMachine.Expiry = ®isterRequest.Expiry
|
||||
}
|
||||
|
||||
h.registrationCache.Set(
|
||||
newMachine.NodeKey,
|
||||
newMachine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
h.handleNewMachineCommon(writer, req, registerRequest, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine is already registered, so we need to pass through reauth or key update.
|
||||
if machine != nil {
|
||||
// If the NodeKey stored in headscale is the same as the key presented in a registration
|
||||
// request, then we have a node that is either:
|
||||
// - Trying to log out (sending a expiry in the past)
|
||||
// - A valid, registered machine, looking for the node map
|
||||
// - Expired machine wanting to reauthenticate
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.NodeKey) {
|
||||
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||
if !registerRequest.Expiry.IsZero() &&
|
||||
registerRequest.Expiry.UTC().Before(now) {
|
||||
h.handleMachineLogOutCommon(writer, req, *machine, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If machine is not expired, and is register, we have a already accepted this machine,
|
||||
// let it proceed with a valid registration
|
||||
if !machine.isExpired() {
|
||||
h.handleMachineValidRegistrationCommon(writer, req, *machine, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||
if machine.NodeKey == NodePublicKeyStripPrefix(registerRequest.OldNodeKey) &&
|
||||
!machine.isExpired() {
|
||||
h.handleMachineRefreshKeyCommon(
|
||||
writer,
|
||||
req,
|
||||
registerRequest,
|
||||
*machine,
|
||||
machineKey,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The machine has expired
|
||||
h.handleMachineExpiredCommon(writer, req, registerRequest, *machine, machineKey)
|
||||
|
||||
machine.Expiry = &time.Time{}
|
||||
h.registrationCache.Set(
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
*machine,
|
||||
registerCacheExpiration,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleAuthKeyCommon contains the logic to manage auth key client registration
|
||||
// It is used both by the legacy and the new Noise protocol.
|
||||
// When using Noise, the machineKey is Zero.
|
||||
//
|
||||
// TODO: check if any locks are needed around IP allocation.
|
||||
func (h *Headscale) handleAuthKeyCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
log.Debug().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
resp.MachineAuthorized = false
|
||||
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Failed authentication via AuthKey")
|
||||
|
||||
if pak != nil {
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
} else {
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", "unknown").Inc()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Authentication key was valid, proceeding to acquire IP addresses")
|
||||
|
||||
nodeKey := NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
// retrieve machine information if it exist
|
||||
// The error is not important, because if it does not
|
||||
// exist, then this is a new machine and we will move
|
||||
// on to registration.
|
||||
machine, _ := h.GetMachineByAnyNodeKey(registerRequest.NodeKey, registerRequest.OldNodeKey)
|
||||
if machine != nil {
|
||||
log.Trace().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("machine was already registered before, refreshing with new auth key")
|
||||
|
||||
machine.NodeKey = nodeKey
|
||||
machine.AuthKeyID = uint(pak.ID)
|
||||
err := h.RefreshMachine(machine, registerRequest.Expiry)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to refresh machine")
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
now := time.Now().UTC()
|
||||
|
||||
givenName, err := h.GenerateGivenName(registerRequest.Hostinfo.Hostname)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("func", "RegistrationHandler").
|
||||
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
|
||||
Err(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
machineToRegister := Machine{
|
||||
Hostname: registerRequest.Hostinfo.Hostname,
|
||||
GivenName: givenName,
|
||||
NamespaceID: pak.Namespace.ID,
|
||||
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
||||
RegisterMethod: RegisterMethodAuthKey,
|
||||
Expiry: ®isterRequest.Expiry,
|
||||
NodeKey: nodeKey,
|
||||
LastSeen: &now,
|
||||
AuthKeyID: uint(pak.ID),
|
||||
}
|
||||
|
||||
machine, err = h.RegisterMachine(
|
||||
machineToRegister,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("could not register machine")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = h.UsePreAuthKey(pak)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to use pre-auth key")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.MachineAuthorized = true
|
||||
resp.User = *pak.Namespace.toUser()
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "error", pak.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("new", RegisterMethodAuthKey, "success", pak.Namespace.Name).
|
||||
Inc()
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("func", "handleAuthKeyCommon").
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Str("ips", strings.Join(machine.IPAddresses.ToStringSlice(), ", ")).
|
||||
Msg("Successfully authenticated via AuthKey")
|
||||
}
|
||||
|
||||
// handleNewMachineCommon exposes for both legacy and Noise the functionality to get a URL
|
||||
// for authorizing the machine. This url is then showed to the user by the local Tailscale client.
|
||||
func (h *Headscale) handleNewMachineCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is new, redirect the client to the registration URL
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("The node seems to be new, sending auth url")
|
||||
if h.cfg.OIDC.Issuer != "" {
|
||||
resp.AuthURL = fmt.Sprintf(
|
||||
"%s/oidc/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey),
|
||||
)
|
||||
} else {
|
||||
resp.AuthURL = fmt.Sprintf("%s/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
}
|
||||
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||
Msg("Successfully sent auth url")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineLogOutCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machine Machine,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
log.Info().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client requested logout")
|
||||
|
||||
err := h.ExpireMachine(&machine)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("func", "handleMachineLogOutCommon").
|
||||
Err(err).
|
||||
Msg("Failed to expire machine")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = false
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Successfully logged out")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineValidRegistrationCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machine Machine,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The machine registration is valid, respond with redirect to /map
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.MachineAuthorized = true
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
resp.Login = *machine.Namespace.toLogin()
|
||||
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine successfully authorized")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineRefreshKeyCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine Machine,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
||||
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||
|
||||
if err := h.db.Save(&machine).Error; err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to update machine key in the database")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp.AuthURL = ""
|
||||
resp.User = *machine.Namespace.toUser()
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("node_key", registerRequest.NodeKey.ShortString()).
|
||||
Str("old_node_key", registerRequest.OldNodeKey.ShortString()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine successfully refreshed")
|
||||
}
|
||||
|
||||
func (h *Headscale) handleMachineExpiredCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
registerRequest tailcfg.RegisterRequest,
|
||||
machine Machine,
|
||||
machineKey key.MachinePublic,
|
||||
) {
|
||||
resp := tailcfg.RegisterResponse{}
|
||||
|
||||
// The client has registered before, but has expired
|
||||
log.Debug().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Machine registration has expired. Sending a authurl to register")
|
||||
|
||||
if registerRequest.Auth.AuthKey != "" {
|
||||
h.handleAuthKeyCommon(writer, req, registerRequest, machineKey)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if h.cfg.OIDC.Issuer != "" {
|
||||
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
} else {
|
||||
resp.AuthURL = fmt.Sprintf("%s/register/%s",
|
||||
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||
NodePublicKeyStripPrefix(registerRequest.NodeKey))
|
||||
}
|
||||
|
||||
respBody, err := h.marshalResponse(resp, machineKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Cannot encode message")
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name).
|
||||
Inc()
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name).
|
||||
Inc()
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Bool("noise", machineKey.IsZero()).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Auth URL for reauthenticate successfully sent")
|
||||
}
|
@@ -2,17 +2,12 @@ package headscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,83 +18,15 @@ type contextKey string
|
||||
|
||||
const machineNameContextKey = contextKey("machineName")
|
||||
|
||||
// PollNetMapHandler takes care of /machine/:id/map
|
||||
//
|
||||
// This is the busiest endpoint, as it keeps the HTTP long poll that updates
|
||||
// the clients when something in the network changes.
|
||||
//
|
||||
// The clients POST stuff like HostInfo and their Endpoints here, but
|
||||
// only after their first request (marked with the ReadOnly field).
|
||||
//
|
||||
// At this moment the updates are sent in a quite horrendous way, but they kinda work.
|
||||
func (h *Headscale) PollNetMapHandler(
|
||||
// handlePollCommon is the common code for the legacy and Noise protocols to
|
||||
// managed the poll loop.
|
||||
func (h *Headscale) handlePollCommon(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machine *Machine,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
isNoise bool,
|
||||
) {
|
||||
vars := mux.Vars(req)
|
||||
machineKeyStr, ok := vars["mkey"]
|
||||
if !ok || machineKeyStr == "" {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Msg("No machine key in request")
|
||||
http.Error(writer, "No machine key in request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Msg("PollNetMapHandler called")
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Err(err).
|
||||
Msg("Cannot parse client key")
|
||||
|
||||
http.Error(writer, "Cannot parse client key", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
mapRequest := tailcfg.MapRequest{}
|
||||
err = decode(body, &mapRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Err(err).
|
||||
Msg("Cannot decode message")
|
||||
http.Error(writer, "Cannot decode message", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Ignoring request, cannot find machine with key %s", machineKey.String())
|
||||
|
||||
http.Error(writer, "", http.StatusUnauthorized)
|
||||
|
||||
return
|
||||
}
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String())
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Found machine in database")
|
||||
|
||||
machine.Hostname = mapRequest.Hostinfo.Hostname
|
||||
machine.HostInfo = HostInfo(*mapRequest.Hostinfo)
|
||||
machine.DiscoKey = DiscoPublicKeyStripPrefix(mapRequest.DiscoKey)
|
||||
@@ -107,11 +34,11 @@ func (h *Headscale) PollNetMapHandler(
|
||||
|
||||
// update ACLRules with peer informations (to update server tags if necessary)
|
||||
if h.aclPolicy != nil {
|
||||
err = h.UpdateACLRules()
|
||||
err := h.UpdateACLRules()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("func", "handleAuthKey").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Err(err)
|
||||
}
|
||||
@@ -133,7 +60,8 @@ func (h *Headscale) PollNetMapHandler(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Bool("noise", isNoise).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to persist/update machine in the database")
|
||||
@@ -143,11 +71,12 @@ func (h *Headscale) PollNetMapHandler(
|
||||
}
|
||||
}
|
||||
|
||||
data, err := h.getMapResponse(machineKey, mapRequest, machine)
|
||||
mapResp, err := h.getMapResponseData(mapRequest, machine, isNoise)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Bool("noise", isNoise).
|
||||
Str("node_key", machine.NodeKey).
|
||||
Str("machine", machine.Hostname).
|
||||
Err(err).
|
||||
Msg("Failed to get Map response")
|
||||
@@ -163,7 +92,7 @@ func (h *Headscale) PollNetMapHandler(
|
||||
// Details on the protocol can be found in https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L696
|
||||
log.Debug().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Bool("readOnly", mapRequest.ReadOnly).
|
||||
Bool("omitPeers", mapRequest.OmitPeers).
|
||||
@@ -173,12 +102,13 @@ func (h *Headscale) PollNetMapHandler(
|
||||
if mapRequest.ReadOnly {
|
||||
log.Info().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client is starting up. Probably interested in a DERP map")
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err := writer.Write(data)
|
||||
_, err := writer.Write(mapResp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
@@ -186,20 +116,24 @@ func (h *Headscale) PollNetMapHandler(
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
if f, ok := writer.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// There has been an update to _any_ of the nodes that the other nodes would
|
||||
// need to know about
|
||||
h.setLastStateChangeToNow(machine.Namespace.Name)
|
||||
h.setLastStateChangeToNow()
|
||||
|
||||
// The request is not ReadOnly, so we need to set up channels for updating
|
||||
// peers via longpoll
|
||||
|
||||
// Only create update channel if it has not been created
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Caller().
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Loading or creating update channel")
|
||||
|
||||
@@ -214,11 +148,12 @@ func (h *Headscale) PollNetMapHandler(
|
||||
if mapRequest.OmitPeers && !mapRequest.Stream {
|
||||
log.Info().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client sent endpoint update and is ok with a response without peer list")
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, err := writer.Write(data)
|
||||
_, err := writer.Write(mapResp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
@@ -235,6 +170,7 @@ func (h *Headscale) PollNetMapHandler(
|
||||
} else if mapRequest.OmitPeers && mapRequest.Stream {
|
||||
log.Warn().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Ignoring request, don't know how to handle it")
|
||||
http.Error(writer, "", http.StatusBadRequest)
|
||||
@@ -244,51 +180,54 @@ func (h *Headscale) PollNetMapHandler(
|
||||
|
||||
log.Info().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Client is ready to access the tailnet")
|
||||
log.Info().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Sending initial map")
|
||||
pollDataChan <- data
|
||||
pollDataChan <- mapResp
|
||||
|
||||
log.Info().
|
||||
Str("handler", "PollNetMap").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Notifying peers")
|
||||
updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "full-update").
|
||||
Inc()
|
||||
updateChan <- struct{}{}
|
||||
|
||||
h.PollNetMapStream(
|
||||
h.pollNetMapStream(
|
||||
writer,
|
||||
req,
|
||||
machine,
|
||||
mapRequest,
|
||||
machineKey,
|
||||
pollDataChan,
|
||||
keepAliveChan,
|
||||
updateChan,
|
||||
isNoise,
|
||||
)
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Finished stream, closing PollNetMap session")
|
||||
}
|
||||
|
||||
// PollNetMapStream takes care of /machine/:id/map
|
||||
// stream logic, ensuring we communicate updates and data
|
||||
// to the connected clients.
|
||||
func (h *Headscale) PollNetMapStream(
|
||||
// pollNetMapStream stream logic for /machine/map,
|
||||
// ensuring we communicate updates and data to the connected clients.
|
||||
func (h *Headscale) pollNetMapStream(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
machine *Machine,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machineKey key.MachinePublic,
|
||||
pollDataChan chan []byte,
|
||||
keepAliveChan chan []byte,
|
||||
updateChan chan struct{},
|
||||
isNoise bool,
|
||||
) {
|
||||
h.pollNetMapStreamWG.Add(1)
|
||||
defer h.pollNetMapStreamWG.Done()
|
||||
@@ -302,18 +241,20 @@ func (h *Headscale) PollNetMapStream(
|
||||
ctx,
|
||||
updateChan,
|
||||
keepAliveChan,
|
||||
machineKey,
|
||||
mapRequest,
|
||||
machine,
|
||||
isNoise,
|
||||
)
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("handler", "pollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("Waiting for data to stream...")
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("handler", "pollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msgf("pollData is %#v, keepAliveChan is %#v, updateChan is %#v", pollDataChan, keepAliveChan, updateChan)
|
||||
|
||||
@@ -322,6 +263,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
case data := <-pollDataChan:
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Int("bytes", len(data)).
|
||||
@@ -330,6 +272,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Err(err).
|
||||
@@ -343,6 +286,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Msg("Cannot cast writer to http.Flusher")
|
||||
@@ -352,6 +296,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Int("bytes", len(data)).
|
||||
@@ -363,6 +308,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Err(err).
|
||||
@@ -383,6 +329,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Err(err).
|
||||
@@ -393,6 +340,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "pollData").
|
||||
Int("bytes", len(data)).
|
||||
@@ -409,6 +357,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Err(err).
|
||||
@@ -421,6 +370,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Msg("Cannot cast writer to http.Flusher")
|
||||
@@ -430,6 +380,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Int("bytes", len(data)).
|
||||
@@ -441,6 +392,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Err(err).
|
||||
@@ -456,6 +408,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Err(err).
|
||||
@@ -466,6 +419,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "keepAlive").
|
||||
Int("bytes", len(data)).
|
||||
@@ -474,6 +428,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
case <-updateChan:
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Msg("Received a request for update")
|
||||
@@ -487,14 +442,16 @@ func (h *Headscale) PollNetMapStream(
|
||||
}
|
||||
log.Debug().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Time("last_successful_update", lastUpdate).
|
||||
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
||||
Msgf("There has been updates since the last successful update to %s", machine.Hostname)
|
||||
data, err := h.getMapResponse(machineKey, mapRequest, machine)
|
||||
data, err := h.getMapResponseData(mapRequest, machine, false)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Err(err).
|
||||
@@ -506,6 +463,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Err(err).
|
||||
@@ -521,6 +479,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
log.Error().
|
||||
Caller().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Msg("Cannot cast writer to http.Flusher")
|
||||
@@ -530,6 +489,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Msg("Updated Map has been sent")
|
||||
@@ -547,6 +507,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Err(err).
|
||||
@@ -566,6 +527,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "update").
|
||||
Err(err).
|
||||
@@ -580,6 +542,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Time("last_successful_update", lastUpdate).
|
||||
Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)).
|
||||
@@ -598,6 +561,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "Done").
|
||||
Err(err).
|
||||
@@ -613,6 +577,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Str("channel", "Done").
|
||||
Err(err).
|
||||
@@ -625,6 +590,7 @@ func (h *Headscale) PollNetMapStream(
|
||||
case <-h.shutdownChan:
|
||||
log.Info().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Bool("noise", isNoise).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("The long-poll handler is shutting down")
|
||||
|
||||
@@ -637,9 +603,9 @@ func (h *Headscale) scheduledPollWorker(
|
||||
ctx context.Context,
|
||||
updateChan chan struct{},
|
||||
keepAliveChan chan []byte,
|
||||
machineKey key.MachinePublic,
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *Machine,
|
||||
isNoise bool,
|
||||
) {
|
||||
keepAliveTicker := time.NewTicker(keepAliveInterval)
|
||||
updateCheckerTicker := time.NewTicker(h.cfg.NodeUpdateCheckInterval)
|
||||
@@ -661,10 +627,11 @@ func (h *Headscale) scheduledPollWorker(
|
||||
return
|
||||
|
||||
case <-keepAliveTicker.C:
|
||||
data, err := h.getMapKeepAliveResponse(machineKey, mapRequest)
|
||||
data, err := h.getMapKeepAliveResponseData(mapRequest, machine, isNoise)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("func", "keepAlive").
|
||||
Bool("noise", isNoise).
|
||||
Err(err).
|
||||
Msg("Error generating the keep alive msg")
|
||||
|
||||
@@ -674,6 +641,7 @@ func (h *Headscale) scheduledPollWorker(
|
||||
log.Debug().
|
||||
Str("func", "keepAlive").
|
||||
Str("machine", machine.Hostname).
|
||||
Bool("noise", isNoise).
|
||||
Msg("Sending keepalive")
|
||||
keepAliveChan <- data
|
||||
|
||||
@@ -681,6 +649,7 @@ func (h *Headscale) scheduledPollWorker(
|
||||
log.Debug().
|
||||
Str("func", "scheduledPollWorker").
|
||||
Str("machine", machine.Hostname).
|
||||
Bool("noise", isNoise).
|
||||
Msg("Sending update request")
|
||||
updateRequestsFromNode.WithLabelValues(machine.Namespace.Name, machine.Hostname, "scheduled-update").
|
||||
Inc()
|
120
protocol_common_utils.go
Normal file
120
protocol_common_utils.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
func (h *Headscale) getMapResponseData(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *Machine,
|
||||
isNoise bool,
|
||||
) ([]byte, error) {
|
||||
mapResponse, err := h.generateMapResponse(mapRequest, machine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isNoise {
|
||||
return h.marshalMapResponse(mapResponse, key.MachinePublic{}, mapRequest.Compress)
|
||||
}
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err = machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse client key")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h.marshalMapResponse(mapResponse, machineKey, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (h *Headscale) getMapKeepAliveResponseData(
|
||||
mapRequest tailcfg.MapRequest,
|
||||
machine *Machine,
|
||||
isNoise bool,
|
||||
) ([]byte, error) {
|
||||
keepAliveResponse := tailcfg.MapResponse{
|
||||
KeepAlive: true,
|
||||
}
|
||||
|
||||
if isNoise {
|
||||
return h.marshalMapResponse(keepAliveResponse, key.MachinePublic{}, mapRequest.Compress)
|
||||
}
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machine.MachineKey)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse client key")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h.marshalMapResponse(keepAliveResponse, machineKey, mapRequest.Compress)
|
||||
}
|
||||
|
||||
func (h *Headscale) marshalResponse(
|
||||
resp interface{},
|
||||
machineKey key.MachinePublic,
|
||||
) ([]byte, error) {
|
||||
jsonBody, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot marshal response")
|
||||
}
|
||||
|
||||
if machineKey.IsZero() { // if Noise
|
||||
return jsonBody, nil
|
||||
}
|
||||
|
||||
return h.privateKey.SealTo(machineKey, jsonBody), nil
|
||||
}
|
||||
|
||||
func (h *Headscale) marshalMapResponse(
|
||||
resp interface{},
|
||||
machineKey key.MachinePublic,
|
||||
compression string,
|
||||
) ([]byte, error) {
|
||||
jsonBody, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot marshal map response")
|
||||
}
|
||||
|
||||
var respBody []byte
|
||||
if compression == ZstdCompression {
|
||||
encoder, _ := zstd.NewWriter(nil)
|
||||
respBody = encoder.EncodeAll(jsonBody, nil)
|
||||
if !machineKey.IsZero() { // if legacy protocol
|
||||
respBody = h.privateKey.SealTo(machineKey, respBody)
|
||||
}
|
||||
} else {
|
||||
if !machineKey.IsZero() { // if legacy protocol
|
||||
respBody = h.privateKey.SealTo(machineKey, jsonBody)
|
||||
} else {
|
||||
respBody = jsonBody
|
||||
}
|
||||
}
|
||||
|
||||
data := make([]byte, reservedResponseHeaderSize)
|
||||
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
||||
data = append(data, respBody...)
|
||||
|
||||
return data, nil
|
||||
}
|
58
protocol_legacy.go
Normal file
58
protocol_legacy.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// RegistrationHandler handles the actual registration process of a machine
|
||||
// Endpoint /machine/:mkey.
|
||||
func (h *Headscale) RegistrationHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
vars := mux.Vars(req)
|
||||
machineKeyStr, ok := vars["mkey"]
|
||||
if !ok || machineKeyStr == "" {
|
||||
log.Error().
|
||||
Str("handler", "RegistrationHandler").
|
||||
Msg("No machine ID in request")
|
||||
http.Error(writer, "No machine ID in request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse machine key")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot parse machine key", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
registerRequest := tailcfg.RegisterRequest{}
|
||||
err = decode(body, ®isterRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot decode message")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Cannot decode message", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.handleRegisterCommon(writer, req, registerRequest, machineKey)
|
||||
}
|
94
protocol_legacy_poll.go
Normal file
94
protocol_legacy_poll.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// PollNetMapHandler takes care of /machine/:id/map
|
||||
//
|
||||
// This is the busiest endpoint, as it keeps the HTTP long poll that updates
|
||||
// the clients when something in the network changes.
|
||||
//
|
||||
// The clients POST stuff like HostInfo and their Endpoints here, but
|
||||
// only after their first request (marked with the ReadOnly field).
|
||||
//
|
||||
// At this moment the updates are sent in a quite horrendous way, but they kinda work.
|
||||
func (h *Headscale) PollNetMapHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
vars := mux.Vars(req)
|
||||
machineKeyStr, ok := vars["mkey"]
|
||||
if !ok || machineKeyStr == "" {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Msg("No machine key in request")
|
||||
http.Error(writer, "No machine key in request", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Msg("PollNetMapHandler called")
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
var machineKey key.MachinePublic
|
||||
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Err(err).
|
||||
Msg("Cannot parse client key")
|
||||
|
||||
http.Error(writer, "Cannot parse client key", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
mapRequest := tailcfg.MapRequest{}
|
||||
err = decode(body, &mapRequest, &machineKey, h.privateKey)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Err(err).
|
||||
Msg("Cannot decode message")
|
||||
http.Error(writer, "Cannot decode message", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Ignoring request, cannot find machine with key %s", machineKey.String())
|
||||
|
||||
http.Error(writer, "", http.StatusUnauthorized)
|
||||
|
||||
return
|
||||
}
|
||||
log.Error().
|
||||
Str("handler", "PollNetMap").
|
||||
Msgf("Failed to fetch machine from the database with Machine key: %s", machineKey.String())
|
||||
http.Error(writer, "", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMap").
|
||||
Str("id", machineKeyStr).
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("A machine is entering polling via the legacy protocol")
|
||||
|
||||
h.handlePollCommon(writer, req, machine, mapRequest, false)
|
||||
}
|
38
protocol_noise.go
Normal file
38
protocol_noise.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// // NoiseRegistrationHandler handles the actual registration process of a machine.
|
||||
func (h *Headscale) NoiseRegistrationHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
log.Trace().Caller().Msgf("Noise registration handler for client %s", req.RemoteAddr)
|
||||
if req.Method != http.MethodPost {
|
||||
http.Error(writer, "Wrong method", http.StatusMethodNotAllowed)
|
||||
|
||||
return
|
||||
}
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
registerRequest := tailcfg.RegisterRequest{}
|
||||
if err := json.Unmarshal(body, ®isterRequest); err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse RegisterRequest")
|
||||
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.handleRegisterCommon(writer, req, registerRequest, key.MachinePublic{})
|
||||
}
|
67
protocol_noise_poll.go
Normal file
67
protocol_noise_poll.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// NoisePollNetMapHandler takes care of /machine/:id/map using the Noise protocol
|
||||
//
|
||||
// This is the busiest endpoint, as it keeps the HTTP long poll that updates
|
||||
// the clients when something in the network changes.
|
||||
//
|
||||
// The clients POST stuff like HostInfo and their Endpoints here, but
|
||||
// only after their first request (marked with the ReadOnly field).
|
||||
//
|
||||
// At this moment the updates are sent in a quite horrendous way, but they kinda work.
|
||||
func (h *Headscale) NoisePollNetMapHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
log.Trace().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Msg("PollNetMapHandler called")
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
mapRequest := tailcfg.MapRequest{}
|
||||
if err := json.Unmarshal(body, &mapRequest); err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse MapRequest")
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
machine, err := h.GetMachineByAnyNodeKey(mapRequest.NodeKey, key.NodePublic{})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Warn().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Msgf("Ignoring request, cannot find machine with key %s", mapRequest.NodeKey.String())
|
||||
http.Error(writer, "Internal error", http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
log.Error().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Msgf("Failed to fetch machine from the database with node key: %s", mapRequest.NodeKey.String())
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
log.Debug().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Str("machine", machine.Hostname).
|
||||
Msg("A machine is entering polling via the Noise protocol")
|
||||
|
||||
h.handlePollCommon(writer, req, machine, mapRequest, true)
|
||||
}
|
11
routes.go
11
routes.go
@@ -2,8 +2,7 @@ package headscale
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,7 +15,7 @@ const (
|
||||
func (h *Headscale) GetAdvertisedNodeRoutes(
|
||||
namespace string,
|
||||
nodeName string,
|
||||
) (*[]netaddr.IPPrefix, error) {
|
||||
) (*[]netip.Prefix, error) {
|
||||
machine, err := h.GetMachine(namespace, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -31,7 +30,7 @@ func (h *Headscale) GetAdvertisedNodeRoutes(
|
||||
func (h *Headscale) GetEnabledNodeRoutes(
|
||||
namespace string,
|
||||
nodeName string,
|
||||
) ([]netaddr.IPPrefix, error) {
|
||||
) ([]netip.Prefix, error) {
|
||||
machine, err := h.GetMachine(namespace, nodeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -47,7 +46,7 @@ func (h *Headscale) IsNodeRouteEnabled(
|
||||
nodeName string,
|
||||
routeStr string,
|
||||
) bool {
|
||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -79,7 +78,7 @@ func (h *Headscale) EnableNodeRoute(
|
||||
return err
|
||||
}
|
||||
|
||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||
route, err := netip.ParsePrefix(routeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
@@ -16,11 +17,11 @@ func (s *Suite) TestGetRoutes(c *check.C) {
|
||||
_, err = app.GetMachine("test", "test_get_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netaddr.ParseIPPrefix("10.0.0.0/24")
|
||||
route, err := netip.ParsePrefix("10.0.0.0/24")
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
RoutableIPs: []netaddr.IPPrefix{route},
|
||||
RoutableIPs: []netip.Prefix{route},
|
||||
}
|
||||
|
||||
machine := Machine{
|
||||
@@ -60,18 +61,18 @@ func (s *Suite) TestGetEnableRoutes(c *check.C) {
|
||||
_, err = app.GetMachine("test", "test_enable_route_machine")
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
route, err := netaddr.ParseIPPrefix(
|
||||
route, err := netip.ParsePrefix(
|
||||
"10.0.0.0/24",
|
||||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
route2, err := netaddr.ParseIPPrefix(
|
||||
route2, err := netip.ParsePrefix(
|
||||
"150.0.10.0/25",
|
||||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
hostInfo := tailcfg.Hostinfo{
|
||||
RoutableIPs: []netaddr.IPPrefix{route, route2},
|
||||
RoutableIPs: []netip.Prefix{route, route2},
|
||||
}
|
||||
|
||||
machine := Machine{
|
||||
|
55
utils.go
55
utils.go
@@ -13,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -21,7 +22,7 @@ import (
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"inet.af/netaddr"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
@@ -59,6 +60,8 @@ const (
|
||||
privateHexPrefix = "privkey:"
|
||||
|
||||
PermissionFallback = 0o700
|
||||
|
||||
ZstdCompression = "zstd"
|
||||
)
|
||||
|
||||
func MachinePublicKeyStripPrefix(machineKey key.MachinePublic) string {
|
||||
@@ -116,7 +119,10 @@ func decode(
|
||||
pubKey *key.MachinePublic,
|
||||
privKey *key.MachinePrivate,
|
||||
) error {
|
||||
log.Trace().Int("length", len(msg)).Msg("Trying to decrypt")
|
||||
log.Trace().
|
||||
Str("pubkey", pubKey.ShortString()).
|
||||
Int("length", len(msg)).
|
||||
Msg("Trying to decrypt")
|
||||
|
||||
decrypted, ok := privKey.OpenFrom(*pubKey, msg)
|
||||
if !ok {
|
||||
@@ -130,25 +136,12 @@ func decode(
|
||||
return nil
|
||||
}
|
||||
|
||||
func encode(
|
||||
v interface{},
|
||||
pubKey *key.MachinePublic,
|
||||
privKey *key.MachinePrivate,
|
||||
) ([]byte, error) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey.SealTo(*pubKey, b), nil
|
||||
}
|
||||
|
||||
func (h *Headscale) getAvailableIPs() (MachineAddresses, error) {
|
||||
var ips MachineAddresses
|
||||
var err error
|
||||
ipPrefixes := h.cfg.IPPrefixes
|
||||
for _, ipPrefix := range ipPrefixes {
|
||||
var ip *netaddr.IP
|
||||
var ip *netip.Addr
|
||||
ip, err = h.getAvailableIP(ipPrefix)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
@@ -159,16 +152,16 @@ func (h *Headscale) getAvailableIPs() (MachineAddresses, error) {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
func GetIPPrefixEndpoints(na netaddr.IPPrefix) (netaddr.IP, netaddr.IP) {
|
||||
var network, broadcast netaddr.IP
|
||||
ipRange := na.Range()
|
||||
func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
|
||||
var network, broadcast netip.Addr
|
||||
ipRange := netipx.RangeOfPrefix(na)
|
||||
network = ipRange.From()
|
||||
broadcast = ipRange.To()
|
||||
|
||||
return network, broadcast
|
||||
}
|
||||
|
||||
func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, error) {
|
||||
func (h *Headscale) getAvailableIP(ipPrefix netip.Prefix) (*netip.Addr, error) {
|
||||
usedIps, err := h.getUsedIPs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -189,7 +182,7 @@ func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, erro
|
||||
fallthrough
|
||||
case usedIps.Contains(ip):
|
||||
fallthrough
|
||||
case ip.IsZero() || ip.IsLoopback():
|
||||
case ip == netip.Addr{} || ip.IsLoopback():
|
||||
ip = ip.Next()
|
||||
|
||||
continue
|
||||
@@ -200,19 +193,19 @@ func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, erro
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
|
||||
func (h *Headscale) getUsedIPs() (*netipx.IPSet, error) {
|
||||
// FIXME: This really deserves a better data model,
|
||||
// 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)
|
||||
|
||||
var ips netaddr.IPSetBuilder
|
||||
var ips netipx.IPSetBuilder
|
||||
for _, slice := range addressesSlices {
|
||||
var machineAddresses MachineAddresses
|
||||
err := machineAddresses.Scan(slice)
|
||||
if err != nil {
|
||||
return &netaddr.IPSet{}, fmt.Errorf(
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to read ip from database: %w",
|
||||
err,
|
||||
)
|
||||
@@ -225,7 +218,7 @@ func (h *Headscale) getUsedIPs() (*netaddr.IPSet, error) {
|
||||
|
||||
ipSet, err := ips.IPSet()
|
||||
if err != nil {
|
||||
return &netaddr.IPSet{}, fmt.Errorf(
|
||||
return &netipx.IPSet{}, fmt.Errorf(
|
||||
"failed to build IP Set: %w",
|
||||
err,
|
||||
)
|
||||
@@ -258,7 +251,7 @@ func GrpcSocketDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
return d.DialContext(ctx, "unix", addr)
|
||||
}
|
||||
|
||||
func ipPrefixToString(prefixes []netaddr.IPPrefix) []string {
|
||||
func ipPrefixToString(prefixes []netip.Prefix) []string {
|
||||
result := make([]string, len(prefixes))
|
||||
|
||||
for index, prefix := range prefixes {
|
||||
@@ -268,13 +261,13 @@ func ipPrefixToString(prefixes []netaddr.IPPrefix) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func stringToIPPrefix(prefixes []string) ([]netaddr.IPPrefix, error) {
|
||||
result := make([]netaddr.IPPrefix, len(prefixes))
|
||||
func stringToIPPrefix(prefixes []string) ([]netip.Prefix, error) {
|
||||
result := make([]netip.Prefix, len(prefixes))
|
||||
|
||||
for index, prefixStr := range prefixes {
|
||||
prefix, err := netaddr.ParseIPPrefix(prefixStr)
|
||||
prefix, err := netip.ParsePrefix(prefixStr)
|
||||
if err != nil {
|
||||
return []netaddr.IPPrefix{}, err
|
||||
return []netip.Prefix{}, err
|
||||
}
|
||||
|
||||
result[index] = prefix
|
||||
@@ -283,7 +276,7 @@ func stringToIPPrefix(prefixes []string) ([]netaddr.IPPrefix, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func contains[T string | netaddr.IPPrefix](ts []T, t T) bool {
|
||||
func contains[T string | netip.Prefix](ts []T, t T) bool {
|
||||
for _, v := range ts {
|
||||
if reflect.DeepEqual(v, t) {
|
||||
return true
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package headscale
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
"gopkg.in/check.v1"
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
@@ -10,7 +13,7 @@ func (s *Suite) TestGetAvailableIp(c *check.C) {
|
||||
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
expected := netaddr.MustParseIP("10.27.0.1")
|
||||
expected := netip.MustParseAddr("10.27.0.1")
|
||||
|
||||
c.Assert(len(ips), check.Equals, 1)
|
||||
c.Assert(ips[0].String(), check.Equals, expected.String())
|
||||
@@ -46,8 +49,8 @@ func (s *Suite) TestGetUsedIps(c *check.C) {
|
||||
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
expected := netaddr.MustParseIP("10.27.0.1")
|
||||
expectedIPSetBuilder := netaddr.IPSetBuilder{}
|
||||
expected := netip.MustParseAddr("10.27.0.1")
|
||||
expectedIPSetBuilder := netipx.IPSetBuilder{}
|
||||
expectedIPSetBuilder.Add(expected)
|
||||
expectedIPSet, _ := expectedIPSetBuilder.IPSet()
|
||||
|
||||
@@ -96,11 +99,11 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
usedIps, err := app.getUsedIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
expected0 := netaddr.MustParseIP("10.27.0.1")
|
||||
expected9 := netaddr.MustParseIP("10.27.0.10")
|
||||
expected300 := netaddr.MustParseIP("10.27.0.45")
|
||||
expected0 := netip.MustParseAddr("10.27.0.1")
|
||||
expected9 := netip.MustParseAddr("10.27.0.10")
|
||||
expected300 := netip.MustParseAddr("10.27.0.45")
|
||||
|
||||
notExpectedIPSetBuilder := netaddr.IPSetBuilder{}
|
||||
notExpectedIPSetBuilder := netipx.IPSetBuilder{}
|
||||
notExpectedIPSetBuilder.Add(expected0)
|
||||
notExpectedIPSetBuilder.Add(expected9)
|
||||
notExpectedIPSetBuilder.Add(expected300)
|
||||
@@ -121,7 +124,7 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
c.Assert(
|
||||
machine1.IPAddresses[0],
|
||||
check.Equals,
|
||||
netaddr.MustParseIP("10.27.0.1"),
|
||||
netip.MustParseAddr("10.27.0.1"),
|
||||
)
|
||||
|
||||
machine50, err := app.GetMachineByID(50)
|
||||
@@ -130,10 +133,10 @@ func (s *Suite) TestGetMultiIp(c *check.C) {
|
||||
c.Assert(
|
||||
machine50.IPAddresses[0],
|
||||
check.Equals,
|
||||
netaddr.MustParseIP("10.27.0.50"),
|
||||
netip.MustParseAddr("10.27.0.50"),
|
||||
)
|
||||
|
||||
expectedNextIP := netaddr.MustParseIP("10.27.1.95")
|
||||
expectedNextIP := netip.MustParseAddr("10.27.1.95")
|
||||
nextIP, err := app.getAvailableIPs()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
|
Reference in New Issue
Block a user