mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
tailcfg: define a type for NodeCapability
Instead of untyped string, add a type to identify these. Updates #cleanup Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
3d37328af6
commit
a61caea911
@ -147,7 +147,7 @@ func (e *serveEnv) runFunnel(ctx context.Context, args []string) error {
|
||||
//
|
||||
// verifyFunnelEnabled may refresh the local state and modify the st input.
|
||||
func (e *serveEnv) verifyFunnelEnabled(ctx context.Context, st *ipnstate.Status, port uint16) error {
|
||||
hasFunnelAttrs := func(attrs []string) bool {
|
||||
hasFunnelAttrs := func(attrs []tailcfg.NodeCapability) bool {
|
||||
hasHTTPS := slices.Contains(attrs, tailcfg.CapabilityHTTPS)
|
||||
hasFunnel := slices.Contains(attrs, tailcfg.NodeAttrFunnel)
|
||||
return hasHTTPS && hasFunnel
|
||||
|
@ -269,7 +269,7 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||
// on, enableFeatureInteractive will error. For now, we hide that
|
||||
// error and maintain the previous behavior (prior to 2023-08-15)
|
||||
// of letting them edit the serve config before enabling certs.
|
||||
e.enableFeatureInteractive(ctx, "serve", func(caps []string) bool {
|
||||
e.enableFeatureInteractive(ctx, "serve", func(caps []tailcfg.NodeCapability) bool {
|
||||
return slices.Contains(caps, tailcfg.CapabilityHTTPS)
|
||||
})
|
||||
}
|
||||
@ -829,7 +829,7 @@ func parseServePort(s string) (uint16, error) {
|
||||
//
|
||||
// 2023-08-09: The only valid feature values are "serve" and "funnel".
|
||||
// This can be moved to some CLI lib when expanded past serve/funnel.
|
||||
func (e *serveEnv) enableFeatureInteractive(ctx context.Context, feature string, hasRequiredCapabilities func(caps []string) bool) (err error) {
|
||||
func (e *serveEnv) enableFeatureInteractive(ctx context.Context, feature string, hasRequiredCapabilities func(caps []tailcfg.NodeCapability) bool) (err error) {
|
||||
info, err := e.lc.QueryFeature(ctx, feature)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -233,7 +233,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
|
||||
// on, enableFeatureInteractive will error. For now, we hide that
|
||||
// error and maintain the previous behavior (prior to 2023-08-15)
|
||||
// of letting them edit the serve config before enabling certs.
|
||||
if err := e.enableFeatureInteractive(ctx, "serve", func(caps []string) bool {
|
||||
if err := e.enableFeatureInteractive(ctx, "serve", func(caps []tailcfg.NodeCapability) bool {
|
||||
return slices.Contains(caps, tailcfg.CapabilityHTTPS)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error enabling https feature: %w", err)
|
||||
|
@ -763,7 +763,7 @@ func TestVerifyFunnelEnabled(t *testing.T) {
|
||||
// queryFeatureResponse is the mock response desired from the
|
||||
// call made to lc.QueryFeature by verifyFunnelEnabled.
|
||||
queryFeatureResponse mockQueryFeatureResponse
|
||||
caps []string // optionally set at fakeStatus.Capabilities
|
||||
caps []tailcfg.NodeCapability // optionally set at fakeStatus.Capabilities
|
||||
wantErr string
|
||||
wantPanic string
|
||||
}{
|
||||
@ -780,13 +780,13 @@ func TestVerifyFunnelEnabled(t *testing.T) {
|
||||
{
|
||||
name: "fallback-flow-missing-acl-rule",
|
||||
queryFeatureResponse: mockQueryFeatureResponse{resp: nil, err: errors.New("not-allowed")},
|
||||
caps: []string{tailcfg.CapabilityHTTPS},
|
||||
caps: []tailcfg.NodeCapability{tailcfg.CapabilityHTTPS},
|
||||
wantErr: `Funnel not available; "funnel" node attribute not set. See https://tailscale.com/s/no-funnel.`,
|
||||
},
|
||||
{
|
||||
name: "fallback-flow-enabled",
|
||||
queryFeatureResponse: mockQueryFeatureResponse{resp: nil, err: errors.New("not-allowed")},
|
||||
caps: []string{tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel},
|
||||
caps: []tailcfg.NodeCapability{tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel},
|
||||
wantErr: "", // no error, success
|
||||
},
|
||||
{
|
||||
@ -858,7 +858,7 @@ type fakeLocalServeClient struct {
|
||||
BackendState: ipn.Running.String(),
|
||||
Self: &ipnstate.PeerStatus{
|
||||
DNSName: "foo.test.ts.net",
|
||||
Capabilities: []string{tailcfg.NodeAttrFunnel, tailcfg.CapabilityFunnelPorts + "?ports=443,8443"},
|
||||
Capabilities: []tailcfg.NodeCapability{tailcfg.NodeAttrFunnel, tailcfg.CapabilityFunnelPorts + "?ports=443,8443"},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -329,13 +329,13 @@ func TestUpdatePeersStateFromResponse(t *testing.T) {
|
||||
mapRes: &tailcfg.MapResponse{
|
||||
PeersChangedPatch: []*tailcfg.PeerChange{{
|
||||
NodeID: 1,
|
||||
Capabilities: ptr.To([]string{"foo"}),
|
||||
Capabilities: ptr.To([]tailcfg.NodeCapability{"foo"}),
|
||||
}},
|
||||
},
|
||||
want: peers(&tailcfg.Node{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Capabilities: []string{"foo"},
|
||||
Capabilities: []tailcfg.NodeCapability{"foo"},
|
||||
}),
|
||||
wantStats: updateStats{changed: 1},
|
||||
}}
|
||||
@ -685,15 +685,15 @@ func TestPeerChangeDiff(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "patch-capabilities-to-nonempty",
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []string{"foo"}},
|
||||
b: &tailcfg.Node{ID: 1, Capabilities: []string{"bar"}},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]string{"bar"})},
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
|
||||
b: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"bar"}},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability{"bar"})},
|
||||
},
|
||||
{
|
||||
name: "patch-capabilities-to-empty",
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []string{"foo"}},
|
||||
a: &tailcfg.Node{ID: 1, Capabilities: []tailcfg.NodeCapability{"foo"}},
|
||||
b: &tailcfg.Node{ID: 1},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]string(nil))},
|
||||
want: &tailcfg.PeerChange{NodeID: 1, Capabilities: ptr.To([]tailcfg.NodeCapability(nil))},
|
||||
},
|
||||
{
|
||||
name: "patch-online-to-true",
|
||||
|
@ -48,7 +48,7 @@ type Knobs struct {
|
||||
|
||||
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
|
||||
// node attributes (Node.Capabilities).
|
||||
func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []string) {
|
||||
func (k *Knobs) UpdateFromNodeAttributes(selfNodeAttrs []tailcfg.NodeCapability) {
|
||||
if k == nil {
|
||||
return
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
@ -22,7 +23,7 @@
|
||||
// c2n log level changes), and via capabilities from a NetMap (so users can
|
||||
// enable logging via the ACL JSON).
|
||||
type LogKnob struct {
|
||||
capName string
|
||||
capName tailcfg.NodeCapability
|
||||
cap atomic.Bool
|
||||
env func() bool
|
||||
manual atomic.Bool
|
||||
@ -30,7 +31,7 @@ type LogKnob struct {
|
||||
|
||||
// NewLogKnob creates a new LogKnob, with the provided environment variable
|
||||
// name and/or NetMap capability.
|
||||
func NewLogKnob(env, cap string) *LogKnob {
|
||||
func NewLogKnob(env string, cap tailcfg.NodeCapability) *LogKnob {
|
||||
if env == "" && cap == "" {
|
||||
panic("must provide either an environment variable or capability")
|
||||
}
|
||||
@ -58,7 +59,7 @@ func (lk *LogKnob) Set(v bool) {
|
||||
// about; we use this rather than a concrete type to avoid a circular
|
||||
// dependency.
|
||||
type NetMap interface {
|
||||
SelfCapabilities() views.Slice[string]
|
||||
SelfCapabilities() views.Slice[tailcfg.NodeCapability]
|
||||
}
|
||||
|
||||
// UpdateFromNetMap will enable logging if the SelfNode in the provided NetMap
|
||||
|
@ -64,7 +64,7 @@ func TestLogKnob(t *testing.T) {
|
||||
|
||||
testKnob.UpdateFromNetMap(&netmap.NetworkMap{
|
||||
SelfNode: (&tailcfg.Node{
|
||||
Capabilities: []string{
|
||||
Capabilities: []tailcfg.NodeCapability{
|
||||
"https://tailscale.com/cap/testing",
|
||||
},
|
||||
}).View(),
|
||||
|
@ -4093,7 +4093,7 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) {
|
||||
cc.SetNetInfo(ni)
|
||||
}
|
||||
|
||||
func hasCapability(nm *netmap.NetworkMap, cap string) bool {
|
||||
func hasCapability(nm *netmap.NetworkMap, cap tailcfg.NodeCapability) bool {
|
||||
if nm != nil && nm.SelfNode.Valid() {
|
||||
return views.SliceContains(nm.SelfNode.Capabilities(), cap)
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ type PeerStatus struct {
|
||||
// "https://tailscale.com/cap/is-admin"
|
||||
// "https://tailscale.com/cap/file-sharing"
|
||||
// "funnel"
|
||||
Capabilities []string `json:",omitempty"`
|
||||
Capabilities []tailcfg.NodeCapability `json:",omitempty"`
|
||||
|
||||
// SSH_HostKeys are the node's SSH host keys, if known.
|
||||
SSH_HostKeys []string `json:"sshHostKeys,omitempty"`
|
||||
|
@ -240,7 +240,7 @@ func (sc *ServeConfig) IsFunnelOn() bool {
|
||||
// The nodeAttrs arg should be the node's Self.Capabilities which should contain
|
||||
// the attribute we're checking for and possibly warning-capabilities for
|
||||
// Funnel.
|
||||
func CheckFunnelAccess(port uint16, nodeAttrs []string) error {
|
||||
func CheckFunnelAccess(port uint16, nodeAttrs []tailcfg.NodeCapability) error {
|
||||
if !slices.Contains(nodeAttrs, tailcfg.CapabilityHTTPS) {
|
||||
return errors.New("Funnel not available; HTTPS must be enabled. See https://tailscale.com/s/https.")
|
||||
}
|
||||
@ -253,7 +253,7 @@ func CheckFunnelAccess(port uint16, nodeAttrs []string) error {
|
||||
// CheckFunnelPort checks whether the given port is allowed for Funnel.
|
||||
// It uses the tailcfg.CapabilityFunnelPorts nodeAttr to determine the allowed
|
||||
// ports.
|
||||
func CheckFunnelPort(wantedPort uint16, nodeAttrs []string) error {
|
||||
func CheckFunnelPort(wantedPort uint16, nodeAttrs []tailcfg.NodeCapability) error {
|
||||
deny := func(allowedPorts string) error {
|
||||
if allowedPorts == "" {
|
||||
return fmt.Errorf("port %d is not allowed for funnel", wantedPort)
|
||||
@ -262,7 +262,8 @@ func CheckFunnelPort(wantedPort uint16, nodeAttrs []string) error {
|
||||
}
|
||||
var portsStr string
|
||||
for _, attr := range nodeAttrs {
|
||||
if !strings.HasPrefix(attr, tailcfg.CapabilityFunnelPorts) {
|
||||
attr := string(attr)
|
||||
if !strings.HasPrefix(attr, string(tailcfg.CapabilityFunnelPorts)) {
|
||||
continue
|
||||
}
|
||||
u, err := url.Parse(attr)
|
||||
@ -274,7 +275,7 @@ func CheckFunnelPort(wantedPort uint16, nodeAttrs []string) error {
|
||||
return deny("")
|
||||
}
|
||||
u.RawQuery = ""
|
||||
if u.String() != tailcfg.CapabilityFunnelPorts {
|
||||
if u.String() != string(tailcfg.CapabilityFunnelPorts) {
|
||||
return deny("")
|
||||
}
|
||||
}
|
||||
|
@ -9,20 +9,21 @@
|
||||
)
|
||||
|
||||
func TestCheckFunnelAccess(t *testing.T) {
|
||||
portAttr := "https://tailscale.com/cap/funnel-ports?ports=443,8080-8090,8443,"
|
||||
caps := func(c ...tailcfg.NodeCapability) []tailcfg.NodeCapability { return c }
|
||||
const portAttr tailcfg.NodeCapability = "https://tailscale.com/cap/funnel-ports?ports=443,8080-8090,8443,"
|
||||
tests := []struct {
|
||||
port uint16
|
||||
caps []string
|
||||
caps []tailcfg.NodeCapability
|
||||
wantErr bool
|
||||
}{
|
||||
{443, []string{portAttr}, true}, // No "funnel" attribute
|
||||
{443, []string{portAttr, tailcfg.NodeAttrFunnel}, true},
|
||||
{443, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, false},
|
||||
{8443, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, false},
|
||||
{8321, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, true},
|
||||
{8083, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, false},
|
||||
{8091, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, true},
|
||||
{3000, []string{portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel}, true},
|
||||
{443, caps(portAttr), true}, // No "funnel" attribute
|
||||
{443, caps(portAttr, tailcfg.NodeAttrFunnel), true},
|
||||
{443, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
|
||||
{8443, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
|
||||
{8321, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
|
||||
{8083, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), false},
|
||||
{8091, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
|
||||
{3000, caps(portAttr, tailcfg.CapabilityHTTPS, tailcfg.NodeAttrFunnel), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := CheckFunnelAccess(tt.port, tt.caps)
|
||||
|
@ -12,6 +12,7 @@
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -289,7 +290,7 @@ type Node struct {
|
||||
// such as:
|
||||
// "https://tailscale.com/cap/is-admin"
|
||||
// "https://tailscale.com/cap/file-sharing"
|
||||
Capabilities []string `json:",omitempty"`
|
||||
Capabilities []NodeCapability `json:",omitempty"`
|
||||
|
||||
// UnsignedPeerAPIOnly means that this node is not signed nor subject to TKA
|
||||
// restrictions. However, in exchange for that privilege, it does not get
|
||||
@ -1226,10 +1227,11 @@ type CapGrant struct {
|
||||
CapMap PeerCapMap `json:",omitempty"`
|
||||
}
|
||||
|
||||
// PeerCapability is a capability granted to a node by a FilterRule.
|
||||
// It's a string, but its meaning is application-defined.
|
||||
// It must be a URL, like "https://tailscale.com/cap/file-sharing-target" or
|
||||
// "https://example.com/cap/read-access".
|
||||
// PeerCapability represents a capability granted to a peer by a FilterRule when
|
||||
// the peer communicates with the node that has this rule. Its meaning is
|
||||
// application-defined.
|
||||
//
|
||||
// It must be a URL like "https://tailscale.com/cap/file-send".
|
||||
type PeerCapability string
|
||||
|
||||
const (
|
||||
@ -1838,7 +1840,7 @@ func (n *Node) Equal(n2 *Node) bool {
|
||||
n.Created.Equal(n2.Created) &&
|
||||
eqTimePtr(n.LastSeen, n2.LastSeen) &&
|
||||
n.MachineAuthorized == n2.MachineAuthorized &&
|
||||
eqStrings(n.Capabilities, n2.Capabilities) &&
|
||||
slices.Equal(n.Capabilities, n2.Capabilities) &&
|
||||
n.ComputedName == n2.ComputedName &&
|
||||
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
|
||||
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
|
||||
@ -1911,112 +1913,117 @@ type Oauth2Token struct {
|
||||
Expiry time.Time `json:"expiry,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
// These are the capabilities that the self node has as listed in
|
||||
// MapResponse.Node.Capabilities.
|
||||
//
|
||||
// We've since started referring to these as "Node Attributes" ("nodeAttrs"
|
||||
// in the ACL policy file).
|
||||
// NodeCapability represents a capability granted to the self node as listed in
|
||||
// MapResponse.Node.Capabilities.
|
||||
//
|
||||
// It must be a URL like "https://tailscale.com/cap/file-sharing", or a
|
||||
// well-known capability name like "funnel". The latter is only allowed for
|
||||
// Tailscale-defined capabilities.
|
||||
//
|
||||
// Unlike PeerCapability, NodeCapability is not in context of a peer and is
|
||||
// granted to the node itself.
|
||||
//
|
||||
// These are also referred to as "Node Attributes" in the ACL policy file.
|
||||
type NodeCapability string
|
||||
|
||||
CapabilityFileSharing = "https://tailscale.com/cap/file-sharing"
|
||||
CapabilityAdmin = "https://tailscale.com/cap/is-admin"
|
||||
CapabilitySSH = "https://tailscale.com/cap/ssh" // feature enabled/available
|
||||
CapabilitySSHRuleIn = "https://tailscale.com/cap/ssh-rule-in" // some SSH rule reach this node
|
||||
CapabilityDataPlaneAuditLogs = "https://tailscale.com/cap/data-plane-audit-logs" // feature enabled
|
||||
CapabilityDebug = "https://tailscale.com/cap/debug" // exposes debug endpoints over the PeerAPI
|
||||
CapabilityHTTPS = "https" // https cert provisioning enabled on tailnet
|
||||
const (
|
||||
CapabilityFileSharing NodeCapability = "https://tailscale.com/cap/file-sharing"
|
||||
CapabilityAdmin NodeCapability = "https://tailscale.com/cap/is-admin"
|
||||
CapabilitySSH NodeCapability = "https://tailscale.com/cap/ssh" // feature enabled/available
|
||||
CapabilitySSHRuleIn NodeCapability = "https://tailscale.com/cap/ssh-rule-in" // some SSH rule reach this node
|
||||
CapabilityDataPlaneAuditLogs NodeCapability = "https://tailscale.com/cap/data-plane-audit-logs" // feature enabled
|
||||
CapabilityDebug NodeCapability = "https://tailscale.com/cap/debug" // exposes debug endpoints over the PeerAPI
|
||||
CapabilityHTTPS NodeCapability = "https" // https cert provisioning enabled on tailnet
|
||||
|
||||
// CapabilityBindToInterfaceByRoute changes how Darwin nodes create
|
||||
// sockets (in the net/netns package). See that package for more
|
||||
// details on the behaviour of this capability.
|
||||
CapabilityBindToInterfaceByRoute = "https://tailscale.com/cap/bind-to-interface-by-route"
|
||||
CapabilityBindToInterfaceByRoute NodeCapability = "https://tailscale.com/cap/bind-to-interface-by-route"
|
||||
|
||||
// CapabilityDebugDisableAlternateDefaultRouteInterface changes how Darwin
|
||||
// nodes get the default interface. There is an optional hook (used by the
|
||||
// macOS and iOS clients) to override the default interface, this capability
|
||||
// disables that and uses the default behavior (of parsing the routing
|
||||
// table).
|
||||
CapabilityDebugDisableAlternateDefaultRouteInterface = "https://tailscale.com/cap/debug-disable-alternate-default-route-interface"
|
||||
CapabilityDebugDisableAlternateDefaultRouteInterface NodeCapability = "https://tailscale.com/cap/debug-disable-alternate-default-route-interface"
|
||||
|
||||
// CapabilityDebugDisableBindConnToInterface disables the automatic binding
|
||||
// of connections to the default network interface on Darwin nodes.
|
||||
CapabilityDebugDisableBindConnToInterface = "https://tailscale.com/cap/debug-disable-bind-conn-to-interface"
|
||||
CapabilityDebugDisableBindConnToInterface NodeCapability = "https://tailscale.com/cap/debug-disable-bind-conn-to-interface"
|
||||
|
||||
// CapabilityTailnetLock indicates the node may initialize tailnet lock.
|
||||
CapabilityTailnetLock = "https://tailscale.com/cap/tailnet-lock"
|
||||
CapabilityTailnetLock NodeCapability = "https://tailscale.com/cap/tailnet-lock"
|
||||
|
||||
// Funnel warning capabilities used for reporting errors to the user.
|
||||
|
||||
// CapabilityWarnFunnelNoInvite indicates whether Funnel is enabled for the tailnet.
|
||||
// This cap is no longer used 2023-08-09 onwards.
|
||||
CapabilityWarnFunnelNoInvite = "https://tailscale.com/cap/warn-funnel-no-invite"
|
||||
CapabilityWarnFunnelNoInvite NodeCapability = "https://tailscale.com/cap/warn-funnel-no-invite"
|
||||
|
||||
// CapabilityWarnFunnelNoHTTPS indicates HTTPS has not been enabled for the tailnet.
|
||||
// This cap is no longer used 2023-08-09 onwards.
|
||||
CapabilityWarnFunnelNoHTTPS = "https://tailscale.com/cap/warn-funnel-no-https"
|
||||
CapabilityWarnFunnelNoHTTPS NodeCapability = "https://tailscale.com/cap/warn-funnel-no-https"
|
||||
|
||||
// Debug logging capabilities
|
||||
|
||||
// CapabilityDebugTSDNSResolution enables verbose debug logging for DNS
|
||||
// resolution for Tailscale-controlled domains (the control server, log
|
||||
// server, DERP servers, etc.)
|
||||
CapabilityDebugTSDNSResolution = "https://tailscale.com/cap/debug-ts-dns-resolution"
|
||||
CapabilityDebugTSDNSResolution NodeCapability = "https://tailscale.com/cap/debug-ts-dns-resolution"
|
||||
|
||||
// CapabilityFunnelPorts specifies the ports that the Funnel is available on.
|
||||
// The ports are specified as a comma-separated list of port numbers or port
|
||||
// ranges (e.g. "80,443,8080-8090") in the ports query parameter.
|
||||
// e.g. https://tailscale.com/cap/funnel-ports?ports=80,443,8080-8090
|
||||
CapabilityFunnelPorts = "https://tailscale.com/cap/funnel-ports"
|
||||
)
|
||||
CapabilityFunnelPorts NodeCapability = "https://tailscale.com/cap/funnel-ports"
|
||||
|
||||
const (
|
||||
// NodeAttrFunnel grants the ability for a node to host ingress traffic.
|
||||
NodeAttrFunnel = "funnel"
|
||||
NodeAttrFunnel NodeCapability = "funnel"
|
||||
// NodeAttrSSHAggregator grants the ability for a node to collect SSH sessions.
|
||||
NodeAttrSSHAggregator = "ssh-aggregator"
|
||||
NodeAttrSSHAggregator NodeCapability = "ssh-aggregator"
|
||||
|
||||
// NodeAttrDebugForceBackgroundSTUN forces a node to always do background
|
||||
// STUN queries regardless of inactivity.
|
||||
NodeAttrDebugForceBackgroundSTUN = "debug-always-stun"
|
||||
NodeAttrDebugForceBackgroundSTUN NodeCapability = "debug-always-stun"
|
||||
|
||||
// NodeAttrDebugDisableWGTrim disables the lazy WireGuard configuration,
|
||||
// always giving WireGuard the full netmap, even for idle peers.
|
||||
NodeAttrDebugDisableWGTrim = "debug-no-wg-trim"
|
||||
NodeAttrDebugDisableWGTrim NodeCapability = "debug-no-wg-trim"
|
||||
|
||||
// NodeAttrDebugDisableDRPO disables the DERP Return Path Optimization.
|
||||
// See Issue 150.
|
||||
NodeAttrDebugDisableDRPO = "debug-disable-drpo"
|
||||
NodeAttrDebugDisableDRPO NodeCapability = "debug-disable-drpo"
|
||||
|
||||
// NodeAttrDisableSubnetsIfPAC controls whether subnet routers should be
|
||||
// disabled if WPAD is present on the network.
|
||||
NodeAttrDisableSubnetsIfPAC = "debug-disable-subnets-if-pac"
|
||||
NodeAttrDisableSubnetsIfPAC NodeCapability = "debug-disable-subnets-if-pac"
|
||||
|
||||
// NodeAttrDisableUPnP makes the client not perform a UPnP portmapping.
|
||||
// By default, we want to enable it to see if it works on more clients.
|
||||
//
|
||||
// If UPnP catastrophically fails for people, this should be set kill
|
||||
// new attempts at UPnP connections.
|
||||
NodeAttrDisableUPnP = "debug-disable-upnp"
|
||||
NodeAttrDisableUPnP NodeCapability = "debug-disable-upnp"
|
||||
|
||||
// NodeAttrDisableDeltaUpdates makes the client not process updates via the
|
||||
// delta update mechanism and should instead treat all netmap changes as
|
||||
// "full" ones as tailscaled did in 1.48.x and earlier.
|
||||
NodeAttrDisableDeltaUpdates = "disable-delta-updates"
|
||||
NodeAttrDisableDeltaUpdates NodeCapability = "disable-delta-updates"
|
||||
|
||||
// NodeAttrRandomizeClientPort makes magicsock UDP bind to
|
||||
// :0 to get a random local port, ignoring any configured
|
||||
// fixed port.
|
||||
NodeAttrRandomizeClientPort = "randomize-client-port"
|
||||
NodeAttrRandomizeClientPort NodeCapability = "randomize-client-port"
|
||||
|
||||
// NodeAttrOneCGNATEnable makes the client prefer one big CGNAT /10 route
|
||||
// rather than a /32 per peer. At most one of this or
|
||||
// NodeAttrOneCGNATDisable may be set; if neither are, it's automatic.
|
||||
NodeAttrOneCGNATEnable = "one-cgnat?v=true"
|
||||
NodeAttrOneCGNATEnable NodeCapability = "one-cgnat?v=true"
|
||||
|
||||
// NodeAttrOneCGNATDisable makes the client prefer a /32 route per peer
|
||||
// rather than one big /10 CGNAT route. At most one of this or
|
||||
// NodeAttrOneCGNATEnable may be set; if neither are, it's automatic.
|
||||
NodeAttrOneCGNATDisable = "one-cgnat?v=false"
|
||||
NodeAttrOneCGNATDisable NodeCapability = "one-cgnat?v=false"
|
||||
)
|
||||
|
||||
// SetDNSRequest is a request to add a DNS record.
|
||||
@ -2434,7 +2441,7 @@ type PeerChange struct {
|
||||
// Capabilities, if non-nil, means that the NodeID's capabilities changed.
|
||||
// It's a pointer to a slice for "omitempty", to allow differentiating
|
||||
// a change to empty from no change.
|
||||
Capabilities *[]string `json:",omitempty"`
|
||||
Capabilities *[]NodeCapability `json:",omitempty"`
|
||||
}
|
||||
|
||||
// DerpMagicIP is a fake WireGuard endpoint IP address that means to
|
||||
|
@ -98,7 +98,7 @@ func (src *Node) Clone() *Node {
|
||||
LastSeen *time.Time
|
||||
Online *bool
|
||||
MachineAuthorized bool
|
||||
Capabilities []string
|
||||
Capabilities []NodeCapability
|
||||
UnsignedPeerAPIOnly bool
|
||||
ComputedName string
|
||||
computedHostIfDifferent string
|
||||
|
@ -165,13 +165,13 @@ func (v NodeView) Online() *bool {
|
||||
return &x
|
||||
}
|
||||
|
||||
func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
|
||||
func (v NodeView) Capabilities() views.Slice[string] { return views.SliceOf(v.ж.Capabilities) }
|
||||
func (v NodeView) UnsignedPeerAPIOnly() bool { return v.ж.UnsignedPeerAPIOnly }
|
||||
func (v NodeView) ComputedName() string { return v.ж.ComputedName }
|
||||
func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
|
||||
func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
|
||||
func (v NodeView) Expired() bool { return v.ж.Expired }
|
||||
func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
|
||||
func (v NodeView) Capabilities() views.Slice[NodeCapability] { return views.SliceOf(v.ж.Capabilities) }
|
||||
func (v NodeView) UnsignedPeerAPIOnly() bool { return v.ж.UnsignedPeerAPIOnly }
|
||||
func (v NodeView) ComputedName() string { return v.ж.ComputedName }
|
||||
func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
|
||||
func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
|
||||
func (v NodeView) Expired() bool { return v.ж.Expired }
|
||||
func (v NodeView) SelfNodeV4MasqAddrForThisPeer() *netip.Addr {
|
||||
if v.ж.SelfNodeV4MasqAddrForThisPeer == nil {
|
||||
return nil
|
||||
@ -210,7 +210,7 @@ func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
|
||||
LastSeen *time.Time
|
||||
Online *bool
|
||||
MachineAuthorized bool
|
||||
Capabilities []string
|
||||
Capabilities []NodeCapability
|
||||
UnsignedPeerAPIOnly bool
|
||||
ComputedName string
|
||||
computedHostIfDifferent string
|
||||
|
@ -585,7 +585,7 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.
|
||||
AllowedIPs: allowedIPs,
|
||||
Hostinfo: req.Hostinfo.View(),
|
||||
Name: req.Hostinfo.Hostname,
|
||||
Capabilities: []string{
|
||||
Capabilities: []tailcfg.NodeCapability{
|
||||
tailcfg.CapabilityHTTPS,
|
||||
tailcfg.NodeAttrFunnel,
|
||||
tailcfg.CapabilityFunnelPorts + "?ports=8080,443",
|
||||
|
@ -203,8 +203,8 @@ func (nm *NetworkMap) MagicDNSSuffix() string {
|
||||
// SelfCapabilities returns SelfNode.Capabilities if nm and nm.SelfNode are
|
||||
// non-nil. This is a method so we can use it in envknob/logknob without a
|
||||
// circular dependency.
|
||||
func (nm *NetworkMap) SelfCapabilities() views.Slice[string] {
|
||||
var zero views.Slice[string]
|
||||
func (nm *NetworkMap) SelfCapabilities() views.Slice[tailcfg.NodeCapability] {
|
||||
var zero views.Slice[tailcfg.NodeCapability]
|
||||
if nm == nil || !nm.SelfNode.Valid() {
|
||||
return zero
|
||||
}
|
||||
|
@ -873,7 +873,7 @@ func TestMatchesMatchProtoAndIPsOnlyIfAllPorts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCaps(t *testing.T) {
|
||||
func TestPeerCaps(t *testing.T) {
|
||||
mm, err := MatchesFromFilterRules([]tailcfg.FilterRule{
|
||||
{
|
||||
SrcIPs: []string{"*"},
|
||||
|
Loading…
Reference in New Issue
Block a user