Skip to content

Commit e172c29

Browse files
authored
initial capver packet tracking version (#2391)
* initial capver packet tracking version Signed-off-by: Kristoffer Dalby <[email protected]> * Log the minimum version as client version, not only capver Signed-off-by: Kristoffer Dalby <[email protected]> * remove old versions Signed-off-by: Kristoffer Dalby <[email protected]> * use capver for integration tests Signed-off-by: Kristoffer Dalby <[email protected]> * changelog Signed-off-by: Kristoffer Dalby <[email protected]> * patch through m and n key Signed-off-by: Kristoffer Dalby <[email protected]> --------- Signed-off-by: Kristoffer Dalby <[email protected]>
1 parent cd3b8e6 commit e172c29

File tree

8 files changed

+397
-68
lines changed

8 files changed

+397
-68
lines changed

CHANGELOG.md

+19-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- `oidc.map_legacy_users` is now `false` by default
88
[#2350](https://github.com/juanfont/headscale/pull/2350)
9+
- Print Tailscale version instead of capability versions for outdated nodes
10+
[#2391](https://github.com/juanfont/headscale/pull/2391)
911

1012
## 0.24.2 (2025-01-30)
1113

@@ -24,8 +26,8 @@
2426
[#2367](https://github.com/juanfont/headscale/pull/2367)
2527
- Relax username validation to allow emails
2628
[#2364](https://github.com/juanfont/headscale/pull/2364)
27-
- Remove invalid routes and add stronger constraints for routes to avoid API panic
28-
[#2371](https://github.com/juanfont/headscale/pull/2371)
29+
- Remove invalid routes and add stronger constraints for routes to avoid API
30+
panic [#2371](https://github.com/juanfont/headscale/pull/2371)
2931
- Fix panic when `derp.update_frequency` is 0
3032
[#2368](https://github.com/juanfont/headscale/pull/2368)
3133

@@ -60,8 +62,7 @@ and have it populate to Headscale automatically the next time they log in.
6062
However, this may affect the way you reference users in policies.
6163

6264
Headscale v0.23.0 and earlier never recorded the `iss` and `sub` fields, so all
63-
legacy (existing) OIDC accounts _need to be migrated_ to be properly
64-
secured.
65+
legacy (existing) OIDC accounts _need to be migrated_ to be properly secured.
6566

6667
#### What do I need to do to migrate?
6768

@@ -73,8 +74,8 @@ The migration will mostly be done automatically, with one exception. If your
7374
OIDC does not provide an `email_verified` claim, Headscale will ignore the
7475
`email`. This means that either the administrator will have to mark the user
7576
emails as verified, or ensure the users verify their emails. Any unverified
76-
emails will be ignored, meaning that the users will get new accounts instead
77-
of being migrated.
77+
emails will be ignored, meaning that the users will get new accounts instead of
78+
being migrated.
7879

7980
After this exception is ensured, make all users log into Headscale with their
8081
account, and Headscale will automatically update the account record. This will
@@ -175,7 +176,8 @@ This will also affect the way you
175176
- User gRPC/API [#2261](https://github.com/juanfont/headscale/pull/2261):
176177
- If you depend on a Headscale Web UI, you should wait with this update until
177178
the UI have been updated to match the new API.
178-
- `GET /api/v1/user/{name}` and `GetUser` have been removed in favour of `ListUsers` with an ID parameter
179+
- `GET /api/v1/user/{name}` and `GetUser` have been removed in favour of
180+
`ListUsers` with an ID parameter
179181
- `RenameUser` and `DeleteUser` now require an ID instead of a name.
180182

181183
### Changes
@@ -197,9 +199,12 @@ This will also affect the way you
197199
- CLI for managing users now accepts `--identifier` in addition to `--name`,
198200
usage of `--identifier` is recommended
199201
[#2261](https://github.com/juanfont/headscale/pull/2261)
200-
- Add `dns.extra_records_path` configuration option [#2262](https://github.com/juanfont/headscale/issues/2262)
201-
- Support client verify for DERP [#2046](https://github.com/juanfont/headscale/pull/2046)
202-
- Add PKCE Verifier for OIDC [#2314](https://github.com/juanfont/headscale/pull/2314)
202+
- Add `dns.extra_records_path` configuration option
203+
[#2262](https://github.com/juanfont/headscale/issues/2262)
204+
- Support client verify for DERP
205+
[#2046](https://github.com/juanfont/headscale/pull/2046)
206+
- Add PKCE Verifier for OIDC
207+
[#2314](https://github.com/juanfont/headscale/pull/2314)
203208

204209
## 0.23.0 (2024-09-18)
205210

@@ -730,8 +735,8 @@ behaviour.
730735
- All machines can communicate with all machines by default
731736
- Tags should now work correctly and adding a host to Headscale should now
732737
reload the rules.
733-
- The documentation have a [fictional example](./docs/ref/acls.md) that should cover
734-
some use cases of the ACLs features
738+
- The documentation have a [fictional example](./docs/ref/acls.md) that should
739+
cover some use cases of the ACLs features
735740

736741
### Features
737742

@@ -749,7 +754,8 @@ behaviour.
749754

750755
- Add IPv6 support to the prefix assigned to namespaces
751756
- Add API Key support
752-
- Enable remote control of `headscale` via CLI [docs](./docs/ref/remote-cli.md)
757+
- Enable remote control of `headscale` via CLI
758+
[docs](./docs/ref/remote-cli.md)
753759
- Enable HTTP API (beta, subject to change)
754760
- OpenID Connect users will be mapped per namespaces
755761
- Each user will get its own namespace, created if it does not exist

hscontrol/app.go

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
grpcRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
2525
"github.com/juanfont/headscale"
2626
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
27+
"github.com/juanfont/headscale/hscontrol/capver"
2728
"github.com/juanfont/headscale/hscontrol/db"
2829
"github.com/juanfont/headscale/hscontrol/derp"
2930
derpServer "github.com/juanfont/headscale/hscontrol/derp/server"
@@ -560,6 +561,11 @@ func (h *Headscale) Serve() error {
560561
spew.Dump(h.cfg)
561562
}
562563

564+
log.Info().
565+
Caller().
566+
Str("minimum_version", capver.TailscaleVersion(MinimumCapVersion)).
567+
Msg("Clients with a lower minimum version will be rejected")
568+
563569
// Fetch an initial DERP Map before we start serving
564570
h.DERPMap = derp.GetDERPMap(h.cfg.DERP)
565571
h.mapper = mapper.NewMapper(h.db, h.cfg, h.DERPMap, h.nodeNotifier, h.polMan)

hscontrol/capver/capver.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package capver
2+
3+
import (
4+
"sort"
5+
"strings"
6+
7+
xmaps "golang.org/x/exp/maps"
8+
"tailscale.com/tailcfg"
9+
"tailscale.com/util/set"
10+
)
11+
12+
func tailscaleVersSorted() []string {
13+
vers := xmaps.Keys(tailscaleToCapVer)
14+
sort.Strings(vers)
15+
return vers
16+
}
17+
18+
func capVersSorted() []tailcfg.CapabilityVersion {
19+
capVers := xmaps.Keys(capVerToTailscaleVer)
20+
sort.Slice(capVers, func(i, j int) bool {
21+
return capVers[i] < capVers[j]
22+
})
23+
return capVers
24+
}
25+
26+
// TailscaleVersion returns the Tailscale version for the given CapabilityVersion.
27+
func TailscaleVersion(ver tailcfg.CapabilityVersion) string {
28+
return capVerToTailscaleVer[ver]
29+
}
30+
31+
// CapabilityVersion returns the CapabilityVersion for the given Tailscale version.
32+
func CapabilityVersion(ver string) tailcfg.CapabilityVersion {
33+
if !strings.HasPrefix(ver, "v") {
34+
ver = "v" + ver
35+
}
36+
return tailscaleToCapVer[ver]
37+
}
38+
39+
// TailscaleLatest returns the n latest Tailscale versions.
40+
func TailscaleLatest(n int) []string {
41+
if n <= 0 {
42+
return nil
43+
}
44+
45+
tsSorted := tailscaleVersSorted()
46+
47+
if n > len(tsSorted) {
48+
return tsSorted
49+
}
50+
51+
return tsSorted[len(tsSorted)-n:]
52+
}
53+
54+
// TailscaleLatestMajorMinor returns the n latest Tailscale versions (e.g. 1.80).
55+
func TailscaleLatestMajorMinor(n int, stripV bool) []string {
56+
if n <= 0 {
57+
return nil
58+
}
59+
60+
majors := set.Set[string]{}
61+
for _, vers := range tailscaleVersSorted() {
62+
if stripV {
63+
vers = strings.TrimPrefix(vers, "v")
64+
}
65+
v := strings.Split(vers, ".")
66+
majors.Add(v[0] + "." + v[1])
67+
}
68+
69+
majorSl := majors.Slice()
70+
sort.Strings(majorSl)
71+
72+
if n > len(majorSl) {
73+
return majorSl
74+
}
75+
76+
return majorSl[len(majorSl)-n:]
77+
}
78+
79+
// CapVerLatest returns the n latest CapabilityVersions.
80+
func CapVerLatest(n int) []tailcfg.CapabilityVersion {
81+
if n <= 0 {
82+
return nil
83+
}
84+
85+
s := capVersSorted()
86+
87+
if n > len(s) {
88+
return s
89+
}
90+
91+
return s[len(s)-n:]
92+
}

hscontrol/capver/capver_generated.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package capver
2+
3+
//Generated DO NOT EDIT
4+
5+
import "tailscale.com/tailcfg"
6+
7+
var tailscaleToCapVer = map[string]tailcfg.CapabilityVersion{
8+
"v1.44.3": 63,
9+
"v1.56.1": 82,
10+
"v1.58.0": 85,
11+
"v1.58.1": 85,
12+
"v1.58.2": 85,
13+
"v1.60.0": 87,
14+
"v1.60.1": 87,
15+
"v1.62.0": 88,
16+
"v1.62.1": 88,
17+
"v1.64.0": 90,
18+
"v1.64.1": 90,
19+
"v1.64.2": 90,
20+
"v1.66.0": 95,
21+
"v1.66.1": 95,
22+
"v1.66.2": 95,
23+
"v1.66.3": 95,
24+
"v1.66.4": 95,
25+
"v1.68.0": 97,
26+
"v1.68.1": 97,
27+
"v1.68.2": 97,
28+
"v1.70.0": 102,
29+
"v1.72.0": 104,
30+
"v1.72.1": 104,
31+
"v1.74.0": 106,
32+
"v1.74.1": 106,
33+
"v1.76.0": 106,
34+
"v1.76.1": 106,
35+
"v1.76.6": 106,
36+
"v1.78.0": 109,
37+
"v1.78.1": 109,
38+
}
39+
40+
41+
var capVerToTailscaleVer = map[tailcfg.CapabilityVersion]string{
42+
63: "v1.44.3",
43+
82: "v1.56.1",
44+
85: "v1.58.0",
45+
87: "v1.60.0",
46+
88: "v1.62.0",
47+
90: "v1.64.0",
48+
95: "v1.66.0",
49+
97: "v1.68.0",
50+
102: "v1.70.0",
51+
104: "v1.72.0",
52+
106: "v1.74.0",
53+
109: "v1.78.0",
54+
}

hscontrol/capver/capver_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package capver
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
"tailscale.com/tailcfg"
8+
)
9+
10+
func TestTailscaleLatestMajorMinor(t *testing.T) {
11+
tests := []struct {
12+
n int
13+
stripV bool
14+
expected []string
15+
}{
16+
{3, false, []string{"v1.74", "v1.76", "v1.78"}},
17+
{2, true, []string{"1.76", "1.78"}},
18+
{0, false, nil},
19+
}
20+
21+
for _, test := range tests {
22+
t.Run("", func(t *testing.T) {
23+
output := TailscaleLatestMajorMinor(test.n, test.stripV)
24+
if diff := cmp.Diff(output, test.expected); diff != "" {
25+
t.Errorf("TailscaleLatestMajorMinor(%d, %v) mismatch (-want +got):\n%s", test.n, test.stripV, diff)
26+
}
27+
})
28+
}
29+
}
30+
31+
func TestCapVerMinimumTailscaleVersion(t *testing.T) {
32+
tests := []struct {
33+
input tailcfg.CapabilityVersion
34+
expected string
35+
}{
36+
{85, "v1.58.0"},
37+
{90, "v1.64.0"},
38+
{95, "v1.66.0"},
39+
{106, "v1.74.0"},
40+
{109, "v1.78.0"},
41+
{9001, ""}, // Test case for a version higher than any in the map
42+
{60, ""}, // Test case for a version lower than any in the map
43+
}
44+
45+
for _, test := range tests {
46+
t.Run("", func(t *testing.T) {
47+
output := TailscaleVersion(test.input)
48+
if output != test.expected {
49+
t.Errorf("CapVerFromTailscaleVersion(%d) = %s; want %s", test.input, output, test.expected)
50+
}
51+
})
52+
}
53+
}

0 commit comments

Comments
 (0)