mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-10 02:31:59 +09:00
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:
committed by
Kristoffer Dalby
parent
77cd81fe33
commit
63123196cc
@@ -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"
|
||||||
)
|
)
|
||||||
@@ -107,7 +108,7 @@ func generateUserProfiles(
|
|||||||
// 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>`
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1892,7 +1894,7 @@ type Grant struct {
|
|||||||
// 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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].
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user