tailcfg,types/netmap: Add (Visible) Services to SelfNode Caps

Updates #40056
This commit is contained in:
Adriano Sela Aviles 2026-04-10 16:10:08 -07:00
parent 5e81840b57
commit 2baeaacd0f
No known key found for this signature in database
GPG Key ID: 28128631BCCBB1BB
2 changed files with 58 additions and 0 deletions

View File

@ -2447,6 +2447,18 @@ type Oauth2Token struct {
// These are also referred to as "Node Attributes" in the ACL policy file.
type NodeCapability string
// NodeCapabilityPrefix is a prefix for [NodeCapMap] keys that share a common
// namespace, where each entry represents a distinct named instance (e.g. one
// per service). The full key is formed by concatenating the prefix with the
// instance name.
type NodeCapabilityPrefix string
// ToAttribute returns the full [NodeCapability] key for the given value under
// this prefix, of the form prefix+value.
func (p NodeCapabilityPrefix) ToAttribute(value string) NodeCapability {
return NodeCapability(string(p) + value)
}
const (
CapabilityFileSharing NodeCapability = "https://tailscale.com/cap/file-sharing"
CapabilityAdmin NodeCapability = "https://tailscale.com/cap/is-admin"
@ -2780,6 +2792,14 @@ const (
NodeAttrCacheNetworkMaps NodeCapability = "cache-network-maps"
)
const (
// NodeAttrPrefixServices is the prefix for per-service [NodeCapMap]
// entries describing Services visible (accessible) to this node. The full
// key for a service named "svc:foo" is NodeAttrPrefixServices+"foo".
// Each value under such a key is of type [ServiceDetails].
NodeAttrPrefixServices NodeCapabilityPrefix = "services/"
)
// SetDNSRequest is a request to add a DNS record.
//
// This is used to let tailscaled clients complete their ACME DNS-01 challenges
@ -3318,6 +3338,19 @@ const LBHeader = "Ts-Lb"
// this client is hosting can be ignored.
type ServiceIPMappings map[ServiceName][]netip.Addr
// ServiceDetails describes a Service visible to this node.
// It is the value type stored under [NodeAttrPrefixServices]+serviceName keys in [NodeCapMap].
type ServiceDetails struct {
// Name is the name of the Service, of the form "svc:dns-label".
Name ServiceName
// Addrs are the IP addresses (IPv4 and IPv6) assigned to this Service.
Addrs []netip.Addr `json:",omitempty"`
// Ports are the protocol/port combinations the Service accepts.
Ports []ProtoPortRange `json:",omitempty"`
}
// ClientAuditAction represents an auditable action that a client can report to the
// control plane. These actions must correspond to the supported actions
// in the control plane.

View File

@ -146,6 +146,31 @@ func (nm *NetworkMap) GetIPVIPServiceMap() IPServiceMappings {
return res
}
// Services returns the Services visible (accessible) to this node,
// decoded from [tailcfg.NodeAttrPrefixServices]+serviceName entries in the
// self node's CapMap. Returns nil if nm is nil or SelfNode is invalid.
func (nm *NetworkMap) Services() map[tailcfg.ServiceName]tailcfg.ServiceDetails {
if nm == nil || !nm.SelfNode.Valid() {
return nil
}
prefix := string(tailcfg.NodeAttrPrefixServices)
var result map[tailcfg.ServiceName]tailcfg.ServiceDetails
for cap, _ := range nm.SelfNode.CapMap().All() {
if !strings.HasPrefix(string(cap), prefix) {
continue
}
svcs, err := tailcfg.UnmarshalNodeCapViewJSON[tailcfg.ServiceDetails](nm.SelfNode.CapMap(), cap)
if err != nil || len(svcs) < 1 {
continue
}
if result == nil {
result = make(map[tailcfg.ServiceName]tailcfg.ServiceDetails)
}
result[svcs[0].Name] = svcs[0]
}
return result
}
// SelfNodeOrZero returns the self node, or a zero value if nm is nil.
func (nm *NetworkMap) SelfNodeOrZero() tailcfg.NodeView {
if nm == nil {