mirror of
https://github.com/juanfont/headscale
synced 2026-08-28 04:23:06 -04:00
all: inline deprecated tailcfg capability aliases
Some checks failed
Build / build-nix (push) Failing after 4s
Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Failing after 3s
Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Failing after 4s
Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Failing after 3s
Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Failing after 1s
Build (main) / container (push) Failing after 0s
Check Generated Files / check-generated (push) Failing after 2s
Build (main) / binaries (amd64, darwin) (push) Failing after 0s
Build (main) / binaries (amd64, linux) (push) Failing after 0s
Build (main) / binaries (arm64, darwin) (push) Failing after 0s
Build (main) / binaries (arm64, linux) (push) Failing after 0s
Deploy docs / deploy (push) Failing after 2s
Nix Flake Checks / gotest (push) Failing after 2s
Nix Flake Checks / build (push) Failing after 3s
Nix Flake Checks / golangci-lint (push) Failing after 2s
Nix Flake Checks / formatting (push) Failing after 1s
NixOS Module Tests / nix-module-check (push) Failing after 2s
Server Tests / servertest (push) Failing after 2s
Some checks failed
Build / build-nix (push) Failing after 4s
Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Failing after 3s
Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Failing after 4s
Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Failing after 3s
Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Failing after 1s
Build (main) / container (push) Failing after 0s
Check Generated Files / check-generated (push) Failing after 2s
Build (main) / binaries (amd64, darwin) (push) Failing after 0s
Build (main) / binaries (amd64, linux) (push) Failing after 0s
Build (main) / binaries (arm64, darwin) (push) Failing after 0s
Build (main) / binaries (arm64, linux) (push) Failing after 0s
Deploy docs / deploy (push) Failing after 2s
Nix Flake Checks / gotest (push) Failing after 2s
Nix Flake Checks / build (push) Failing after 3s
Nix Flake Checks / golangci-lint (push) Failing after 2s
Nix Flake Checks / formatting (push) Failing after 1s
NixOS Module Tests / nix-module-check (push) Failing after 2s
Server Tests / servertest (push) Failing after 2s
go 1.27 vet reports the //go:fix inline directives tailscale added to the tailcfg cap aliases; applied with `go fix -inline ./...`.
This commit is contained in:
parent
77cd81fe33
commit
63123196cc
14 changed files with 189 additions and 172 deletions
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
|
@ -106,8 +107,8 @@ func generateUserProfiles(
|
|||
// path, and "nextdns:no-device-info" suppresses the metadata-appending step.
|
||||
// See https://tailscale.com/docs/integrations/nextdns.
|
||||
const (
|
||||
nextDNSAttrPrefix = "nextdns:"
|
||||
nextDNSAttrNoInfo tailcfg.NodeCapability = "nextdns:no-device-info"
|
||||
nextDNSAttrPrefix = "nextdns:"
|
||||
nextDNSAttrNoInfo nodecap.Cap = "nextdns:no-device-info"
|
||||
)
|
||||
|
||||
// nextDNSProfileRE bounds the characters accepted in a `nextdns:<profile>`
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
|
|
@ -74,10 +75,10 @@ func TestTailNode(t *testing.T) {
|
|||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.FileSharing: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
|
@ -163,10 +164,10 @@ func TestTailNode(t *testing.T) {
|
|||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.FileSharing: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
|
@ -188,10 +189,10 @@ func TestTailNode(t *testing.T) {
|
|||
MachineAuthorized: true,
|
||||
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.FileSharing: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: []tailcfg.RawMessage{tailcfg.RawMessage("false")},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
|
@ -274,10 +275,10 @@ func TestTailNodeBaselineGates(t *testing.T) {
|
|||
AutoUpdate: types.AutoUpdateConfig{Enabled: false},
|
||||
},
|
||||
want: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: autoUpdate(false),
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.FileSharing: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: autoUpdate(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -287,9 +288,9 @@ func TestTailNodeBaselineGates(t *testing.T) {
|
|||
AutoUpdate: types.AutoUpdateConfig{Enabled: false},
|
||||
},
|
||||
want: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: autoUpdate(false),
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: autoUpdate(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -299,10 +300,10 @@ func TestTailNodeBaselineGates(t *testing.T) {
|
|||
AutoUpdate: types.AutoUpdateConfig{Enabled: true},
|
||||
},
|
||||
want: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: autoUpdate(true),
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.FileSharing: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: autoUpdate(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -312,9 +313,9 @@ func TestTailNodeBaselineGates(t *testing.T) {
|
|||
AutoUpdate: types.AutoUpdateConfig{Enabled: true},
|
||||
},
|
||||
want: tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
tailcfg.NodeAttrDefaultAutoUpdate: autoUpdate(true),
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
nodecap.DefaultAutoUpdate: autoUpdate(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -351,7 +352,7 @@ func TestTailNodeBaselineGates(t *testing.T) {
|
|||
func TestTailNodeDisableIPv4(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const NodeAttrDisableIPv4 tailcfg.NodeCapability = "disable-ipv4"
|
||||
const NodeAttrDisableIPv4 nodecap.Cap = "disable-ipv4"
|
||||
|
||||
v4 := iap("100.64.0.1")
|
||||
v6Addr := netip.MustParseAddr("fd7a:115c:a1e0::1")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
)
|
||||
|
||||
func TestMatchFromStrings(t *testing.T) {
|
||||
|
|
@ -194,7 +195,7 @@ func TestMatchFromFilterRule(t *testing.T) {
|
|||
netip.MustParsePrefix("100.64.0.3/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: nil,
|
||||
peercap.Relay: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -218,7 +219,7 @@ func TestMatchFromFilterRule(t *testing.T) {
|
|||
netip.MustParsePrefix("100.64.0.2/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelayTarget: nil,
|
||||
peercap.RelayTarget: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -242,7 +243,7 @@ func TestMatchFromFilterRule(t *testing.T) {
|
|||
netip.MustParsePrefix("100.64.0.3/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: nil,
|
||||
peercap.Relay: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
|
@ -157,7 +159,7 @@ func (pol *Policy) compileNodeAttrs(
|
|||
}
|
||||
|
||||
result := make(map[types.NodeID]tailcfg.NodeCapMap)
|
||||
stamp := func(id types.NodeID, attr tailcfg.NodeCapability) {
|
||||
stamp := func(id types.NodeID, attr nodecap.Cap) {
|
||||
capMap, ok := result[id]
|
||||
if !ok {
|
||||
capMap = tailcfg.NodeCapMap{}
|
||||
|
|
@ -190,7 +192,7 @@ func (pol *Policy) compileNodeAttrs(
|
|||
|
||||
if pol.RandomizeClientPort {
|
||||
for _, ni := range nodeList {
|
||||
stamp(ni.id, tailcfg.NodeAttrRandomizeClientPort)
|
||||
stamp(ni.id, nodecap.RandomizeClientPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -598,7 +600,7 @@ func collectRelayTargetIPs(grants []compiledGrant) (*netipx.IPSet, error) {
|
|||
for i := range grants {
|
||||
for _, rule := range grants[i].rules {
|
||||
for _, cg := range rule.CapGrant {
|
||||
if _, ok := cg.CapMap[tailcfg.PeerCapabilityRelay]; !ok {
|
||||
if _, ok := cg.CapMap[peercap.Relay]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
|
|
@ -24,8 +25,8 @@ var (
|
|||
// companionCap pairs a well-known Tailscale capability with its
|
||||
// companion capability.
|
||||
type companionCap struct {
|
||||
original tailcfg.PeerCapability
|
||||
companion tailcfg.PeerCapability
|
||||
original peercap.Cap
|
||||
companion peercap.Cap
|
||||
}
|
||||
|
||||
// companionCaps lists certain well-known Tailscale capabilities and
|
||||
|
|
@ -35,8 +36,8 @@ type companionCap struct {
|
|||
// The slice is ordered by the original capability name so that
|
||||
// generated companion rules are emitted deterministically.
|
||||
var companionCaps = []companionCap{
|
||||
{tailcfg.PeerCapabilityTaildrive, tailcfg.PeerCapabilityTaildriveSharer},
|
||||
{tailcfg.PeerCapabilityRelay, tailcfg.PeerCapabilityRelayTarget},
|
||||
{peercap.Taildrive, peercap.TaildriveSharer},
|
||||
{peercap.Relay, peercap.RelayTarget},
|
||||
}
|
||||
|
||||
// companionCapGrantRules returns additional [tailcfg.FilterRule]s for any
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"go4.org/netipx"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
)
|
||||
|
||||
// aliasWithPorts creates an AliasWithPorts structure from an alias and ports.
|
||||
|
|
@ -3166,7 +3167,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
dstIPStrings: []string{"100.64.0.1"},
|
||||
srcPrefixes: []netip.Prefix{mp("100.64.0.2/32")},
|
||||
capMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildrive: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Taildrive: {tailcfg.RawMessage(`{}`)},
|
||||
},
|
||||
want: []tailcfg.FilterRule{
|
||||
{
|
||||
|
|
@ -3175,7 +3176,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
{
|
||||
Dsts: []netip.Prefix{mp("100.64.0.2/32")},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildriveSharer: nil,
|
||||
peercap.TaildriveSharer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3187,7 +3188,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
dstIPStrings: []string{"100.64.0.10"},
|
||||
srcPrefixes: []netip.Prefix{mp("100.64.0.20/32")},
|
||||
capMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Relay: {tailcfg.RawMessage(`{}`)},
|
||||
},
|
||||
want: []tailcfg.FilterRule{
|
||||
{
|
||||
|
|
@ -3196,7 +3197,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
{
|
||||
Dsts: []netip.Prefix{mp("100.64.0.20/32")},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelayTarget: nil,
|
||||
peercap.RelayTarget: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3208,8 +3209,8 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
dstIPStrings: []string{"100.64.0.1"},
|
||||
srcPrefixes: []netip.Prefix{mp("100.64.0.2/32")},
|
||||
capMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: {tailcfg.RawMessage(`{}`)},
|
||||
tailcfg.PeerCapabilityTaildrive: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Relay: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Taildrive: {tailcfg.RawMessage(`{}`)},
|
||||
},
|
||||
want: []tailcfg.FilterRule{
|
||||
{
|
||||
|
|
@ -3219,7 +3220,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
{
|
||||
Dsts: []netip.Prefix{mp("100.64.0.2/32")},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildriveSharer: nil,
|
||||
peercap.TaildriveSharer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3230,7 +3231,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
{
|
||||
Dsts: []netip.Prefix{mp("100.64.0.2/32")},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelayTarget: nil,
|
||||
peercap.RelayTarget: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3251,7 +3252,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
dstIPStrings: []string{"100.64.0.5"},
|
||||
srcPrefixes: []netip.Prefix{mp("100.64.0.6/32")},
|
||||
capMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildrive: {
|
||||
peercap.Taildrive: {
|
||||
tailcfg.RawMessage(`{"access":"rw"}`),
|
||||
},
|
||||
},
|
||||
|
|
@ -3262,7 +3263,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
{
|
||||
Dsts: []netip.Prefix{mp("100.64.0.6/32")},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildriveSharer: nil,
|
||||
peercap.TaildriveSharer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3280,7 +3281,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
mp("100.64.0.21/32"),
|
||||
},
|
||||
capMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Relay: {tailcfg.RawMessage(`{}`)},
|
||||
},
|
||||
want: []tailcfg.FilterRule{
|
||||
{
|
||||
|
|
@ -3292,7 +3293,7 @@ func TestCompanionCapGrantRules(t *testing.T) {
|
|||
mp("100.64.0.21/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelayTarget: nil,
|
||||
peercap.RelayTarget: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -3805,7 +3806,7 @@ func TestCompileViaGrant(t *testing.T) {
|
|||
Sources: Aliases{up("testuser@")},
|
||||
Destinations: Aliases{pp("10.0.0.0/24")},
|
||||
App: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: {tailcfg.RawMessage(`{}`)},
|
||||
peercap.Relay: {tailcfg.RawMessage(`{}`)},
|
||||
},
|
||||
Via: []Tag{"tag:relay"},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
)
|
||||
|
||||
// nodeAttrsTestUsers returns a minimal user set: two passkey-style users on
|
||||
|
|
@ -81,7 +82,7 @@ const nodeAttrsTagOwners = `"tag:server": ["alice@example.com"],
|
|||
func TestNodeAttrsCompile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
capMap := func(c tailcfg.NodeCapability) tailcfg.NodeCapMap {
|
||||
capMap := func(c nodecap.Cap) tailcfg.NodeCapMap {
|
||||
return tailcfg.NodeCapMap{c: nil}
|
||||
}
|
||||
|
||||
|
|
@ -95,18 +96,18 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
name: "wildcard target hits every node",
|
||||
extra: `"nodeAttrs": [{"target": ["*"], "attr": ["randomize-client-port"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
2: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
3: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
4: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
5: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
1: capMap(nodecap.RandomizeClientPort),
|
||||
2: capMap(nodecap.RandomizeClientPort),
|
||||
3: capMap(nodecap.RandomizeClientPort),
|
||||
4: capMap(nodecap.RandomizeClientPort),
|
||||
5: capMap(nodecap.RandomizeClientPort),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user target hits only that user's untagged nodes",
|
||||
extra: `"nodeAttrs": [{"target": ["alice@example.com"], "attr": ["randomize-client-port"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
1: capMap(nodecap.RandomizeClientPort),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -114,8 +115,8 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
extra: `"nodeAttrs": [{"target": ["tag:server"], "attr": ["drive:share", "drive:access"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
3: {
|
||||
tailcfg.NodeAttrsTaildriveShare: nil,
|
||||
tailcfg.NodeAttrsTaildriveAccess: nil,
|
||||
nodecap.TaildriveShare: nil,
|
||||
nodecap.TaildriveAccess: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -123,17 +124,17 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
name: "autogroup:member hits untagged nodes only",
|
||||
extra: `"nodeAttrs": [{"target": ["autogroup:member"], "attr": ["randomize-client-port"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
2: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
1: capMap(nodecap.RandomizeClientPort),
|
||||
2: capMap(nodecap.RandomizeClientPort),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "autogroup:tagged hits tagged nodes only",
|
||||
extra: `"nodeAttrs": [{"target": ["autogroup:tagged"], "attr": ["disable-captive-portal-detection"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
3: capMap(tailcfg.NodeAttrDisableCaptivePortalDetection),
|
||||
4: capMap(tailcfg.NodeAttrDisableCaptivePortalDetection),
|
||||
5: capMap(tailcfg.NodeAttrDisableCaptivePortalDetection),
|
||||
3: capMap(nodecap.DisableCaptivePortalDetection),
|
||||
4: capMap(nodecap.DisableCaptivePortalDetection),
|
||||
5: capMap(nodecap.DisableCaptivePortalDetection),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -143,14 +144,14 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
{"target": ["tag:server"], "attr": ["drive:share"]}
|
||||
]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrsTaildriveAccess),
|
||||
2: capMap(tailcfg.NodeAttrsTaildriveAccess),
|
||||
1: capMap(nodecap.TaildriveAccess),
|
||||
2: capMap(nodecap.TaildriveAccess),
|
||||
3: {
|
||||
tailcfg.NodeAttrsTaildriveAccess: nil,
|
||||
tailcfg.NodeAttrsTaildriveShare: nil,
|
||||
nodecap.TaildriveAccess: nil,
|
||||
nodecap.TaildriveShare: nil,
|
||||
},
|
||||
4: capMap(tailcfg.NodeAttrsTaildriveAccess),
|
||||
5: capMap(tailcfg.NodeAttrsTaildriveAccess),
|
||||
4: capMap(nodecap.TaildriveAccess),
|
||||
5: capMap(nodecap.TaildriveAccess),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -162,11 +163,11 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
name: "top-level randomizeClientPort stamps every node",
|
||||
extra: `"randomizeClientPort": true`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
2: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
3: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
4: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
5: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
1: capMap(nodecap.RandomizeClientPort),
|
||||
2: capMap(nodecap.RandomizeClientPort),
|
||||
3: capMap(nodecap.RandomizeClientPort),
|
||||
4: capMap(nodecap.RandomizeClientPort),
|
||||
5: capMap(nodecap.RandomizeClientPort),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -174,14 +175,14 @@ func TestNodeAttrsCompile(t *testing.T) {
|
|||
extra: `"randomizeClientPort": true,
|
||||
"nodeAttrs": [{"target": ["tag:server"], "attr": ["disable-captive-portal-detection"]}]`,
|
||||
want: map[types.NodeID]tailcfg.NodeCapMap{
|
||||
1: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
2: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
1: capMap(nodecap.RandomizeClientPort),
|
||||
2: capMap(nodecap.RandomizeClientPort),
|
||||
3: {
|
||||
tailcfg.NodeAttrRandomizeClientPort: nil,
|
||||
tailcfg.NodeAttrDisableCaptivePortalDetection: nil,
|
||||
nodecap.RandomizeClientPort: nil,
|
||||
nodecap.DisableCaptivePortalDetection: nil,
|
||||
},
|
||||
4: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
5: capMap(tailcfg.NodeAttrRandomizeClientPort),
|
||||
4: capMap(nodecap.RandomizeClientPort),
|
||||
5: capMap(nodecap.RandomizeClientPort),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
)
|
||||
|
||||
// PeerCapMap returns the subset of peerSelfCaps the Tailscale client
|
||||
|
|
@ -47,12 +48,12 @@ func PeerCapMap(peer types.NodeView, peerSelfCaps tailcfg.NodeCapMap) tailcfg.No
|
|||
// the suggestion from following an advertised-but-not-yet-trusted
|
||||
// node.
|
||||
if peer.IsExitNode() {
|
||||
if v, ok := peerSelfCaps[tailcfg.NodeAttrSuggestExitNode]; ok {
|
||||
if v, ok := peerSelfCaps[nodecap.SuggestExitNode]; ok {
|
||||
if out == nil {
|
||||
out = tailcfg.NodeCapMap{}
|
||||
}
|
||||
|
||||
out[tailcfg.NodeAttrSuggestExitNode] = v
|
||||
out[nodecap.SuggestExitNode] = v
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ func PeerCapMap(peer types.NodeView, peerSelfCaps tailcfg.NodeCapMap) tailcfg.No
|
|||
// anonymized capture.
|
||||
// 4. Caps that are internal magicsock or embedded-SSH tuning with no
|
||||
// headscale-side equivalent.
|
||||
var unmodelledTailnetStateCaps = []tailcfg.NodeCapability{
|
||||
var unmodelledTailnetStateCaps = []nodecap.Cap{
|
||||
// --- 1. User-role gated ---
|
||||
|
||||
// [tailcfg.CapabilityAdmin]: the hosted control plane stamps this
|
||||
|
|
@ -80,13 +81,13 @@ var unmodelledTailnetStateCaps = []tailcfg.NodeCapability{
|
|||
// the always-on baseline. Stripping on both sides keeps the diff
|
||||
// from failing on every user-owned non-admin node in a capture.
|
||||
// Long-term fix is autogroup:admin support.
|
||||
tailcfg.CapabilityAdmin,
|
||||
nodecap.Admin,
|
||||
|
||||
// [tailcfg.CapabilityOwner]: same shape as is-admin, conditional
|
||||
// on the "owner" role rather than admin. Headscale does not emit
|
||||
// this cap at all. autogroup:owner support is tracked under
|
||||
// NO_USER_ROLES — see the compat skip list.
|
||||
tailcfg.CapabilityOwner,
|
||||
nodecap.Owner,
|
||||
|
||||
// --- 2. Feature not implemented ---
|
||||
|
||||
|
|
@ -95,23 +96,23 @@ var unmodelledTailnetStateCaps = []tailcfg.NodeCapability{
|
|||
// re-keying by the control plane. Client reads at
|
||||
// ipn/ipnlocal/local.go:1752 (b.capTailnetLock). Headscale has no
|
||||
// tailnet-lock implementation.
|
||||
tailcfg.CapabilityTailnetLock,
|
||||
nodecap.TailnetLock,
|
||||
|
||||
// [tailcfg.NodeAttrServiceHost]: marks a node as approved to host
|
||||
// VIP services (Tailscale Services). Client reads via
|
||||
// UnmarshalNodeCapViewJSON at ipn/ipnlocal/local.go:2704.
|
||||
// Headscale does not implement Tailscale Services.
|
||||
tailcfg.NodeAttrServiceHost,
|
||||
nodecap.ServiceHost,
|
||||
|
||||
// [tailcfg.NodeAttrStoreAppCRoutes]: tells an app-connector node
|
||||
// to persist learned routes across restarts. Client reads via
|
||||
// controlknobs:148. Headscale does not implement app connectors.
|
||||
tailcfg.NodeAttrStoreAppCRoutes,
|
||||
nodecap.StoreAppCRoutes,
|
||||
|
||||
// [tailcfg.CapabilityWarnFunnelNoHTTPS]: deprecated in Tailscale
|
||||
// 2023-08-09. Should not appear in fresh captures — listed
|
||||
// defensively in case a stale tailnet still emits it.
|
||||
tailcfg.CapabilityWarnFunnelNoHTTPS,
|
||||
nodecap.WarnFunnelNoHTTPS,
|
||||
|
||||
// --- 3. Tailnet-state metadata not derivable from headscale config ---
|
||||
|
||||
|
|
@ -121,36 +122,36 @@ var unmodelledTailnetStateCaps = []tailcfg.NodeCapability{
|
|||
// from cfg.Domain() that does not round-trip through the
|
||||
// anonymized capture string. Skip rather than diverge on a value
|
||||
// with no real-world equivalent.
|
||||
tailcfg.NodeAttrTailnetDisplayName,
|
||||
nodecap.TailnetDisplayName,
|
||||
|
||||
// [tailcfg.NodeAttrMaxKeyDuration]: tailnet-wide max key duration
|
||||
// value. Headscale has cfg.Node.Expiry but does not surface it
|
||||
// as a cap today; the hosted control plane emits this only when
|
||||
// a non-default value is configured.
|
||||
tailcfg.NodeAttrMaxKeyDuration,
|
||||
nodecap.MaxKeyDuration,
|
||||
|
||||
// [tailcfg.NodeAttrNativeIPV4]: peer-consumed cap conditional on
|
||||
// tailnet ipv4 reachability state. Out of scope for the current
|
||||
// peer-cap adoption (only suggest-exit-node is wired in this
|
||||
// PR).
|
||||
tailcfg.NodeAttrNativeIPV4,
|
||||
nodecap.NativeIPV4,
|
||||
|
||||
// --- 4. Internal tuning, no headscale equivalent ---
|
||||
|
||||
// [tailcfg.NodeAttrProbeUDPLifetime]: tunes magicsock's UDP
|
||||
// path-lifetime probe behavior. Internal performance knob; not
|
||||
// policy-driven. Client reads via controlknobs:147.
|
||||
tailcfg.NodeAttrProbeUDPLifetime,
|
||||
nodecap.ProbeUDPLifetime,
|
||||
|
||||
// [tailcfg.NodeAttrSSHBehaviorV1]: configures the embedded SSH
|
||||
// server (no su, in-process SFTP). Internal tuning; the embedded
|
||||
// server picks Tailscale-vendored defaults without the cap.
|
||||
tailcfg.NodeAttrSSHBehaviorV1,
|
||||
nodecap.SSHBehaviorV1,
|
||||
|
||||
// [tailcfg.NodeAttrSSHEnvironmentVariables]: gates SendEnv
|
||||
// forwarding in the embedded SSH server. Internal; default chosen
|
||||
// by the server.
|
||||
tailcfg.NodeAttrSSHEnvironmentVariables,
|
||||
nodecap.SSHEnvironmentVariables,
|
||||
}
|
||||
|
||||
// strippedCapPrefixes lists URL/string prefixes for parameterized or
|
||||
|
|
@ -172,7 +173,7 @@ func stripUnmodelledTailnetStateCaps(cm tailcfg.NodeCapMap) tailcfg.NodeCapMap {
|
|||
}
|
||||
|
||||
out := maps.Clone(cm)
|
||||
maps.DeleteFunc(out, func(k tailcfg.NodeCapability, _ []tailcfg.RawMessage) bool {
|
||||
maps.DeleteFunc(out, func(k nodecap.Cap, _ []tailcfg.RawMessage) bool {
|
||||
return isUnmodelledTailnetStateCap(k)
|
||||
})
|
||||
|
||||
|
|
@ -183,7 +184,7 @@ func stripUnmodelledTailnetStateCaps(cm tailcfg.NodeCapMap) tailcfg.NodeCapMap {
|
|||
return out
|
||||
}
|
||||
|
||||
func isUnmodelledTailnetStateCap(k tailcfg.NodeCapability) bool {
|
||||
func isUnmodelledTailnetStateCap(k nodecap.Cap) bool {
|
||||
if slices.Contains(unmodelledTailnetStateCaps, k) {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/juanfont/headscale/hscontrol/types/testcapture"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
|
|
@ -321,7 +322,7 @@ func testNodeAttrsSuccess(
|
|||
// capMapFromView materialises a captured CapMap view into the
|
||||
// [tailcfg.NodeCapMap] shape headscale renders, so both sides of the
|
||||
// diff have the same concrete type.
|
||||
func capMapFromView(view views.MapSlice[tailcfg.NodeCapability, tailcfg.RawMessage]) tailcfg.NodeCapMap {
|
||||
func capMapFromView(view views.MapSlice[nodecap.Cap, tailcfg.RawMessage]) tailcfg.NodeCapMap {
|
||||
if view.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import (
|
|||
"go4.org/netipx"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/util/set"
|
||||
|
|
@ -99,8 +101,8 @@ var (
|
|||
// without the matching server-side machinery would be misleading — nodes
|
||||
// would advertise a feature that does not work. Reject at policy load and
|
||||
// point operators at the issue.
|
||||
var nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{
|
||||
tailcfg.NodeAttrFunnel: "https://github.com/juanfont/headscale/issues/2527",
|
||||
var nodeAttrUnsupportedCaps = map[nodecap.Cap]string{
|
||||
nodecap.Funnel: "https://github.com/juanfont/headscale/issues/2527",
|
||||
}
|
||||
|
||||
// Policy validation errors.
|
||||
|
|
@ -1891,9 +1893,9 @@ type Grant struct {
|
|||
// IPPool is parsed and validated for forward compatibility with the IP
|
||||
// allocator; the policy compiler does not consume it yet.
|
||||
type NodeAttrGrant struct {
|
||||
Targets Aliases `json:"target"`
|
||||
Attrs []tailcfg.NodeCapability `json:"attr,omitempty"`
|
||||
IPPool []netip.Prefix `json:"ipPool,omitempty"`
|
||||
Targets Aliases `json:"target"`
|
||||
Attrs []nodecap.Cap `json:"attr,omitempty"`
|
||||
IPPool []netip.Prefix `json:"ipPool,omitempty"`
|
||||
}
|
||||
|
||||
// aclToGrants converts an [ACL] rule to one or more equivalent [Grant] rules.
|
||||
|
|
@ -2253,7 +2255,7 @@ func validateCapabilityName(name string) error {
|
|||
|
||||
// Reject caps in the tailscale.com domain unless allowlisted.
|
||||
if strings.HasPrefix(name, "tailscale.com/") {
|
||||
if !tailscaleCapAllowlist[tailcfg.PeerCapability(name)] {
|
||||
if !tailscaleCapAllowlist[peercap.Cap(name)] {
|
||||
return ErrCapNameTailscaleDomain
|
||||
}
|
||||
}
|
||||
|
|
@ -2265,16 +2267,16 @@ func validateCapabilityName(name string) error {
|
|||
// that users are allowed to specify in grant app fields. Companion caps
|
||||
// (drive-sharer, relay-target) and internal caps (ingress, funnel) are
|
||||
// generated by the server and cannot be specified by users.
|
||||
var tailscaleCapAllowlist = map[tailcfg.PeerCapability]bool{
|
||||
tailcfg.PeerCapabilityTaildrive: true, // tailscale.com/cap/drive
|
||||
tailcfg.PeerCapabilityRelay: true, // tailscale.com/cap/relay
|
||||
tailcfg.PeerCapabilityWebUI: true, // tailscale.com/cap/webui
|
||||
tailcfg.PeerCapabilityKubernetes: true, // tailscale.com/cap/kubernetes
|
||||
tailcfg.PeerCapabilityTsIDP: true, // tailscale.com/cap/tsidp
|
||||
var tailscaleCapAllowlist = map[peercap.Cap]bool{
|
||||
peercap.Taildrive: true, // tailscale.com/cap/drive
|
||||
peercap.Relay: true, // tailscale.com/cap/relay
|
||||
peercap.WebUI: true, // tailscale.com/cap/webui
|
||||
peercap.Kubernetes: true, // tailscale.com/cap/kubernetes
|
||||
peercap.TsIDP: true, // tailscale.com/cap/tsidp
|
||||
|
||||
// tailscale.com/cap/secrets is the capability used by setec
|
||||
// (github.com/tailscale/setec); allow it so it can be granted via policy.
|
||||
tailcfg.PeerCapability("tailscale.com/cap/secrets"): true,
|
||||
peercap.Cap("tailscale.com/cap/secrets"): true,
|
||||
}
|
||||
|
||||
// validateGrantSrcDstCombination validates [Grant]-specific source/destination
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/wgengine/filter/filtertype"
|
||||
)
|
||||
|
|
@ -157,13 +158,13 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo
|
|||
for _, m := range nm1.PacketFilter {
|
||||
for _, cm := range m.Caps {
|
||||
switch cm.Cap { //nolint:exhaustive // only checking grant-specific caps
|
||||
case tailcfg.PeerCapabilityTaildrive:
|
||||
case peercap.Taildrive:
|
||||
foundDrive = true
|
||||
case tailcfg.PeerCapabilityTaildriveSharer:
|
||||
case peercap.TaildriveSharer:
|
||||
foundDriveSharer = true
|
||||
case tailcfg.PeerCapabilityRelay:
|
||||
case peercap.Relay:
|
||||
foundRelay = true
|
||||
case tailcfg.PeerCapabilityRelayTarget:
|
||||
case peercap.RelayTarget:
|
||||
foundRelayTarget = true
|
||||
}
|
||||
}
|
||||
|
|
@ -464,8 +465,8 @@ func TestGrantPolicies(t *testing.T) { //nolint:gocyclo
|
|||
|
||||
for _, m := range nm1.PacketFilter {
|
||||
for _, cm := range m.Caps {
|
||||
if cm.Cap == tailcfg.PeerCapabilityTaildrive ||
|
||||
cm.Cap == tailcfg.PeerCapabilityTaildriveSharer {
|
||||
if cm.Cap == peercap.Taildrive ||
|
||||
cm.Cap == peercap.TaildriveSharer {
|
||||
foundDriveOrSharer = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/types/netmap"
|
||||
)
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ func reloadPolicy(t *testing.T, srv *servertest.TestServer, pol string) {
|
|||
}
|
||||
|
||||
// hasCap reports whether the given netmap's self CapMap contains want.
|
||||
func hasCap(nm *netmap.NetworkMap, want tailcfg.NodeCapability) bool {
|
||||
func hasCap(nm *netmap.NetworkMap, want nodecap.Cap) bool {
|
||||
if nm == nil || !nm.SelfNode.Valid() {
|
||||
return false
|
||||
}
|
||||
|
|
@ -82,11 +83,11 @@ func TestNodeAttrsDeliverToSelfAndPeer(t *testing.T) {
|
|||
|
||||
c1.WaitForCondition(t, "self randomize-client-port cap on c1", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort)
|
||||
})
|
||||
c2.WaitForCondition(t, "self randomize-client-port cap on c2", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort)
|
||||
})
|
||||
|
||||
// randomize-client-port is not in the peer-consumed allowlist and
|
||||
|
|
@ -121,7 +122,7 @@ func TestNodeAttrsUserTargetIsolated(t *testing.T) {
|
|||
|
||||
a.WaitForCondition(t, "alice gains randomize-client-port", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort)
|
||||
})
|
||||
|
||||
// bob must remain free of the cap; check after alice has converged so we
|
||||
|
|
@ -129,7 +130,7 @@ func TestNodeAttrsUserTargetIsolated(t *testing.T) {
|
|||
b.WaitForPeers(t, 1, 10*time.Second)
|
||||
nmB := b.Netmap()
|
||||
require.NotNil(t, nmB)
|
||||
assert.False(t, hasCap(nmB, tailcfg.NodeAttrRandomizeClientPort),
|
||||
assert.False(t, hasCap(nmB, nodecap.RandomizeClientPort),
|
||||
"bob is not in the target set; must not receive the cap")
|
||||
}
|
||||
|
||||
|
|
@ -154,14 +155,14 @@ func TestNodeAttrsRevokesWhenRemoved(t *testing.T) {
|
|||
|
||||
c.WaitForCondition(t, "captive cap appears", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrDisableCaptivePortalDetection)
|
||||
return hasCap(nm, nodecap.DisableCaptivePortalDetection)
|
||||
})
|
||||
|
||||
reloadPolicy(t, srv, `{}`)
|
||||
|
||||
c.WaitForCondition(t, "captive cap disappears", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return !hasCap(nm, tailcfg.NodeAttrDisableCaptivePortalDetection)
|
||||
return !hasCap(nm, nodecap.DisableCaptivePortalDetection)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -184,11 +185,11 @@ func TestNodeAttrsBaselineCapsAlwaysOn(t *testing.T) {
|
|||
return false
|
||||
}
|
||||
|
||||
for _, w := range []tailcfg.NodeCapability{
|
||||
tailcfg.CapabilityAdmin,
|
||||
tailcfg.CapabilitySSH,
|
||||
tailcfg.CapabilityFileSharing,
|
||||
tailcfg.NodeAttrDefaultAutoUpdate,
|
||||
for _, w := range []nodecap.Cap{
|
||||
nodecap.Admin,
|
||||
nodecap.SSH,
|
||||
nodecap.FileSharing,
|
||||
nodecap.DefaultAutoUpdate,
|
||||
} {
|
||||
if !hasCap(nm, w) {
|
||||
return false
|
||||
|
|
@ -217,9 +218,9 @@ func TestTaildropDisabledWithholdsFileSharingCap(t *testing.T) {
|
|||
return false
|
||||
}
|
||||
|
||||
return !hasCap(nm, tailcfg.CapabilityFileSharing) &&
|
||||
hasCap(nm, tailcfg.CapabilityAdmin) &&
|
||||
hasCap(nm, tailcfg.CapabilitySSH)
|
||||
return !hasCap(nm, nodecap.FileSharing) &&
|
||||
hasCap(nm, nodecap.Admin) &&
|
||||
hasCap(nm, nodecap.SSH)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -247,9 +248,9 @@ func TestNodeAttrsAddsToBaseline(t *testing.T) {
|
|||
|
||||
c.WaitForCondition(t, "policy adds caps on top of baseline", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort) &&
|
||||
hasCap(nm, tailcfg.NodeAttrDisableCaptivePortalDetection) &&
|
||||
hasCap(nm, tailcfg.CapabilitySSH)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort) &&
|
||||
hasCap(nm, nodecap.DisableCaptivePortalDetection) &&
|
||||
hasCap(nm, nodecap.SSH)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -276,7 +277,7 @@ func TestNodeAttrsReloadingSamePolicyDoesNotChurnSelf(t *testing.T) {
|
|||
|
||||
c.WaitForCondition(t, "policy cap arrives", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort)
|
||||
})
|
||||
|
||||
// Reload identical bytes. Per-node CapMap diff produces an empty
|
||||
|
|
@ -288,7 +289,7 @@ func TestNodeAttrsReloadingSamePolicyDoesNotChurnSelf(t *testing.T) {
|
|||
|
||||
c.WaitForCondition(t, "cap persists after no-op reload", 5*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrRandomizeClientPort)
|
||||
return hasCap(nm, nodecap.RandomizeClientPort)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -352,7 +353,7 @@ func TestNodeAttrsSuggestExitNodeOnPeerCapMap(t *testing.T) {
|
|||
// usual stamp; nothing special about exit nodes here).
|
||||
exit.WaitForCondition(t, "self suggest-exit-node on exit", 10*time.Second,
|
||||
func(nm *netmap.NetworkMap) bool {
|
||||
return hasCap(nm, tailcfg.NodeAttrSuggestExitNode)
|
||||
return hasCap(nm, nodecap.SuggestExitNode)
|
||||
})
|
||||
|
||||
// Peer-side: the viewer sees the exit node in its Peers list with
|
||||
|
|
@ -369,7 +370,7 @@ func TestNodeAttrsSuggestExitNodeOnPeerCapMap(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
return peer.CapMap().Contains(tailcfg.NodeAttrSuggestExitNode)
|
||||
return peer.CapMap().Contains(nodecap.SuggestExitNode)
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
@ -389,7 +390,7 @@ func TestNodeAttrsSuggestExitNodeOnPeerCapMap(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
return !peer.CapMap().Contains(tailcfg.NodeAttrSuggestExitNode)
|
||||
return !peer.CapMap().Contains(nodecap.SuggestExitNode)
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"go4.org/netipx"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/dnsname"
|
||||
|
|
@ -39,7 +40,7 @@ type RouteFunc func(id NodeID) []netip.Prefix
|
|||
// node's own IPv4 CGNAT prefix in [tailcfg.Node.Addresses] and
|
||||
// [tailcfg.Node.AllowedIPs]. Subnet routes the node advertises remain.
|
||||
// See https://tailscale.com/docs/reference/troubleshooting/network-configuration/cgnat-conflicts.
|
||||
const nodeAttrDisableIPv4 tailcfg.NodeCapability = "disable-ipv4"
|
||||
const nodeAttrDisableIPv4 nodecap.Cap = "disable-ipv4"
|
||||
|
||||
// filterIPv4 returns ps with every IPv4 prefix dropped. Used by
|
||||
// [NodeView.TailNode] when the node carries the disable-ipv4 nodeAttr.
|
||||
|
|
@ -1200,12 +1201,12 @@ func (nv NodeView) TailNode(
|
|||
// what Tailscale SaaS emits for a default tailnet.
|
||||
// cfg.Taildrop.Enabled gates CapabilityFileSharing.
|
||||
capMap := tailcfg.NodeCapMap{
|
||||
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
||||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
nodecap.Admin: []tailcfg.RawMessage{},
|
||||
nodecap.SSH: []tailcfg.RawMessage{},
|
||||
}
|
||||
|
||||
if cfg.Taildrop.Enabled {
|
||||
capMap[tailcfg.CapabilityFileSharing] = []tailcfg.RawMessage{}
|
||||
capMap[nodecap.FileSharing] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
// default-auto-update is always emitted; the value is a JSON bool
|
||||
|
|
@ -1218,7 +1219,7 @@ func (nv NodeView) TailNode(
|
|||
autoUpdateVal = tailcfg.RawMessage("true")
|
||||
}
|
||||
|
||||
capMap[tailcfg.NodeAttrDefaultAutoUpdate] = []tailcfg.RawMessage{autoUpdateVal}
|
||||
capMap[nodecap.DefaultAutoUpdate] = []tailcfg.RawMessage{autoUpdateVal}
|
||||
|
||||
// Policy nodeAttrs overlay the baseline on the self view. Peers
|
||||
// pass nil; their CapMap is replaced downstream by [policyv2.PeerCapMap].
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailcfg/nodecap"
|
||||
"tailscale.com/tailcfg/peercap"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
// hasCapMatchInPacketFilter checks if any [filter.Match] entry in the packet
|
||||
// filter contains a [filter.CapMatch] with the given capability name.
|
||||
func hasCapMatchInPacketFilter(pf []filter.Match, peerCap tailcfg.PeerCapability) bool {
|
||||
func hasCapMatchInPacketFilter(pf []filter.Match, peerCap peercap.Cap) bool {
|
||||
for _, m := range pf {
|
||||
for _, cm := range m.Caps {
|
||||
if cm.Cap == peerCap {
|
||||
|
|
@ -34,7 +36,7 @@ func hasCapMatchInPacketFilter(pf []filter.Match, peerCap tailcfg.PeerCapability
|
|||
// hasCapMatchForIP checks if any [filter.CapMatch] with the given capability
|
||||
// has a Dst prefix that contains the given IP. This validates that
|
||||
// the cap is directed at the correct node, not just present.
|
||||
func hasCapMatchForIP(pf []filter.Match, peerCap tailcfg.PeerCapability, ip netip.Addr) bool {
|
||||
func hasCapMatchForIP(pf []filter.Match, peerCap peercap.Cap, ip netip.Addr) bool {
|
||||
for _, m := range pf {
|
||||
for _, cm := range m.Caps {
|
||||
if cm.Cap == peerCap && cm.Dst.Contains(ip) {
|
||||
|
|
@ -119,7 +121,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
Sources: policyv2.Aliases{tagp("tag:client-a"), tagp("tag:client-b")},
|
||||
Destinations: policyv2.Aliases{tagp("tag:relay")},
|
||||
App: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: {tailcfg.RawMessage("{}")},
|
||||
peercap.Relay: {tailcfg.RawMessage("{}")},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -280,7 +282,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := relayR.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityRelay, relayIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.Relay, relayIPv4),
|
||||
"Relay R should have cap/relay with Dst matching relay's IP %s", relayIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "R should have cap/relay targeting its own IP")
|
||||
|
||||
|
|
@ -288,7 +290,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := clientA.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityRelayTarget, clientAIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.RelayTarget, clientAIPv4),
|
||||
"Client A should have cap/relay-target with Dst matching A's IP %s", clientAIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "A should have cap/relay-target targeting its own IP")
|
||||
|
||||
|
|
@ -296,7 +298,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := clientB.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityRelayTarget, clientBIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.RelayTarget, clientBIPv4),
|
||||
"Client B should have cap/relay-target with Dst matching B's IP %s", clientBIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "B should have cap/relay-target targeting its own IP")
|
||||
|
||||
|
|
@ -306,7 +308,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := relayR.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityRelayTarget),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.RelayTarget),
|
||||
"Relay R should NOT have cap/relay-target")
|
||||
}, 10*time.Second, 500*time.Millisecond, "R should not have cap/relay-target")
|
||||
|
||||
|
|
@ -314,7 +316,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := clientA.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityRelay),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.Relay),
|
||||
"Client A should NOT have cap/relay")
|
||||
}, 10*time.Second, 500*time.Millisecond, "A should not have cap/relay")
|
||||
|
||||
|
|
@ -322,7 +324,7 @@ func TestGrantCapRelay(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := clientB.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityRelay),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.Relay),
|
||||
"Client B should NOT have cap/relay")
|
||||
}, 10*time.Second, 500*time.Millisecond, "B should not have cap/relay")
|
||||
|
||||
|
|
@ -560,9 +562,9 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
NodeAttrs: []policyv2.NodeAttrGrant{
|
||||
{
|
||||
Targets: policyv2.Aliases{policyv2.Wildcard},
|
||||
Attrs: []tailcfg.NodeCapability{
|
||||
tailcfg.NodeAttrsTaildriveShare,
|
||||
tailcfg.NodeAttrsTaildriveAccess,
|
||||
Attrs: []nodecap.Cap{
|
||||
nodecap.TaildriveShare,
|
||||
nodecap.TaildriveAccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -586,7 +588,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
Sources: policyv2.Aliases{tagp("tag:rw-client")},
|
||||
Destinations: policyv2.Aliases{tagp("tag:sharer")},
|
||||
App: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildrive: {
|
||||
peercap.Taildrive: {
|
||||
tailcfg.RawMessage(`{"shares":["*"],"access":"rw"}`),
|
||||
},
|
||||
},
|
||||
|
|
@ -596,7 +598,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
Sources: policyv2.Aliases{tagp("tag:ro-client")},
|
||||
Destinations: policyv2.Aliases{tagp("tag:sharer")},
|
||||
App: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityTaildrive: {
|
||||
peercap.Taildrive: {
|
||||
tailcfg.RawMessage(`{"shares":["*"],"access":"ro"}`),
|
||||
},
|
||||
},
|
||||
|
|
@ -723,9 +725,9 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
"%s: SelfNode should be valid", node.Hostname())
|
||||
|
||||
if nm.SelfNode.Valid() {
|
||||
assert.True(c, nm.SelfNode.HasCap(tailcfg.NodeAttrsTaildriveShare),
|
||||
assert.True(c, nm.SelfNode.HasCap(nodecap.TaildriveShare),
|
||||
"%s: should have drive:share cap", node.Hostname())
|
||||
assert.True(c, nm.SelfNode.HasCap(tailcfg.NodeAttrsTaildriveAccess),
|
||||
assert.True(c, nm.SelfNode.HasCap(nodecap.TaildriveAccess),
|
||||
"%s: should have drive:access cap", node.Hostname())
|
||||
}
|
||||
}, assertTimeout, 500*time.Millisecond,
|
||||
|
|
@ -741,7 +743,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := sharer.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityTaildrive, sharerIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.Taildrive, sharerIPv4),
|
||||
"Sharer should have cap/drive with Dst matching sharer's IP %s", sharerIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "sharer should have cap/drive targeting its own IP")
|
||||
|
||||
|
|
@ -751,7 +753,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := rwClient.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityTaildriveSharer, rwClientIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.TaildriveSharer, rwClientIPv4),
|
||||
"RW client should have cap/drive-sharer with Dst matching rw-client's IP %s", rwClientIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "rw-client should have cap/drive-sharer")
|
||||
|
||||
|
|
@ -761,7 +763,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := roClient.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.True(c, hasCapMatchForIP(pf, tailcfg.PeerCapabilityTaildriveSharer, roClientIPv4),
|
||||
assert.True(c, hasCapMatchForIP(pf, peercap.TaildriveSharer, roClientIPv4),
|
||||
"RO client should have cap/drive-sharer with Dst matching ro-client's IP %s", roClientIPv4)
|
||||
}, assertTimeout, 500*time.Millisecond, "ro-client should have cap/drive-sharer")
|
||||
|
||||
|
|
@ -771,9 +773,9 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := noAccess.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityTaildrive),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.Taildrive),
|
||||
"no-access should NOT have cap/drive")
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityTaildriveSharer),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.TaildriveSharer),
|
||||
"no-access should NOT have cap/drive-sharer")
|
||||
}, 10*time.Second, 500*time.Millisecond, "no-access should have no drive caps")
|
||||
|
||||
|
|
@ -781,7 +783,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := sharer.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityTaildriveSharer),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.TaildriveSharer),
|
||||
"sharer should NOT have cap/drive-sharer")
|
||||
}, 10*time.Second, 500*time.Millisecond, "sharer should not have cap/drive-sharer")
|
||||
|
||||
|
|
@ -789,7 +791,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := rwClient.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityTaildrive),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.Taildrive),
|
||||
"rw-client should NOT have cap/drive")
|
||||
}, 10*time.Second, 500*time.Millisecond, "rw-client should not have cap/drive")
|
||||
|
||||
|
|
@ -797,7 +799,7 @@ func TestGrantCapDrive(t *testing.T) {
|
|||
assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
pf, err := roClient.PacketFilter()
|
||||
assert.NoError(c, err)
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, tailcfg.PeerCapabilityTaildrive),
|
||||
assert.False(c, hasCapMatchInPacketFilter(pf, peercap.Taildrive),
|
||||
"ro-client should NOT have cap/drive")
|
||||
}, 10*time.Second, 500*time.Millisecond, "ro-client should not have cap/drive")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue