all: inline deprecated tailcfg capability aliases

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