mirror of
https://github.com/juanfont/headscale.git
synced 2025-11-20 01:40:21 -05:00
Exit nodes are now only visible to nodes that have permission to use them according to ACL policy. Previously, exit routes (0.0.0.0/0 and ::/0) were unconditionally added to the AllowedIPs field in the network map, making exit nodes visible to all peers regardless of policy. Changes: - Modified buildTailPeers and WithSelfNode in builder.go to filter exit routes through policy.ReduceRoutes, same as primary routes - Removed unconditional addition of exit routes in tail.go tailNode function - Updated tail_test.go to reflect new behavior where exit routes are filtered The fix ensures that exit nodes are only visible when a node has autogroup:internet in their ACL destination rules. Co-authored-by: kradalby <98431+kradalby@users.noreply.github.com>
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package mapper
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/samber/lo"
|
|
"tailscale.com/net/tsaddr"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/views"
|
|
)
|
|
|
|
// NodeCanHaveTagChecker is an interface for checking if a node can have a tag.
|
|
type NodeCanHaveTagChecker interface {
|
|
NodeCanHaveTag(node types.NodeView, tag string) bool
|
|
}
|
|
|
|
func tailNodes(
|
|
nodes views.Slice[types.NodeView],
|
|
capVer tailcfg.CapabilityVersion,
|
|
checker NodeCanHaveTagChecker,
|
|
primaryRouteFunc routeFilterFunc,
|
|
cfg *types.Config,
|
|
) ([]*tailcfg.Node, error) {
|
|
tNodes := make([]*tailcfg.Node, 0, nodes.Len())
|
|
|
|
for _, node := range nodes.All() {
|
|
tNode, err := tailNode(
|
|
node,
|
|
capVer,
|
|
checker,
|
|
primaryRouteFunc,
|
|
cfg,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tNodes = append(tNodes, tNode)
|
|
}
|
|
|
|
return tNodes, nil
|
|
}
|
|
|
|
// tailNode converts a Node into a Tailscale Node.
|
|
func tailNode(
|
|
node types.NodeView,
|
|
capVer tailcfg.CapabilityVersion,
|
|
checker NodeCanHaveTagChecker,
|
|
primaryRouteFunc routeFilterFunc,
|
|
cfg *types.Config,
|
|
) (*tailcfg.Node, error) {
|
|
addrs := node.Prefixes()
|
|
|
|
var derp int
|
|
|
|
// TODO(kradalby): legacyDERP was removed in tailscale/tailscale@2fc4455e6dd9ab7f879d4e2f7cffc2be81f14077
|
|
// and should be removed after 111 is the minimum capver.
|
|
var legacyDERP string
|
|
if node.Hostinfo().Valid() && node.Hostinfo().NetInfo().Valid() {
|
|
legacyDERP = fmt.Sprintf("127.3.3.40:%d", node.Hostinfo().NetInfo().PreferredDERP())
|
|
derp = node.Hostinfo().NetInfo().PreferredDERP()
|
|
} else {
|
|
legacyDERP = "127.3.3.40:0" // Zero means disconnected or unknown.
|
|
}
|
|
|
|
var keyExpiry time.Time
|
|
if node.Expiry().Valid() {
|
|
keyExpiry = node.Expiry().Get()
|
|
} else {
|
|
keyExpiry = time.Time{}
|
|
}
|
|
|
|
hostname, err := node.GetFQDN(cfg.BaseDomain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var tags []string
|
|
for _, tag := range node.RequestTagsSlice().All() {
|
|
if checker.NodeCanHaveTag(node, tag) {
|
|
tags = append(tags, tag)
|
|
}
|
|
}
|
|
for _, tag := range node.ForcedTags().All() {
|
|
tags = append(tags, tag)
|
|
}
|
|
tags = lo.Uniq(tags)
|
|
|
|
// Get filtered routes (includes both primary routes and exit routes if allowed by policy)
|
|
routes := primaryRouteFunc(node.ID())
|
|
allowed := append(addrs, routes...)
|
|
tsaddr.SortPrefixes(allowed)
|
|
|
|
tNode := tailcfg.Node{
|
|
ID: tailcfg.NodeID(node.ID()), // this is the actual ID
|
|
StableID: node.ID().StableID(),
|
|
Name: hostname,
|
|
Cap: capVer,
|
|
|
|
User: tailcfg.UserID(node.UserID()),
|
|
|
|
Key: node.NodeKey(),
|
|
KeyExpiry: keyExpiry.UTC(),
|
|
|
|
Machine: node.MachineKey(),
|
|
DiscoKey: node.DiscoKey(),
|
|
Addresses: addrs,
|
|
PrimaryRoutes: routes,
|
|
AllowedIPs: allowed,
|
|
Endpoints: node.Endpoints().AsSlice(),
|
|
HomeDERP: derp,
|
|
LegacyDERPString: legacyDERP,
|
|
Hostinfo: node.Hostinfo(),
|
|
Created: node.CreatedAt().UTC(),
|
|
|
|
Online: node.IsOnline().Clone(),
|
|
|
|
Tags: tags,
|
|
|
|
MachineAuthorized: !node.IsExpired(),
|
|
Expired: node.IsExpired(),
|
|
}
|
|
|
|
tNode.CapMap = tailcfg.NodeCapMap{
|
|
tailcfg.CapabilityFileSharing: []tailcfg.RawMessage{},
|
|
tailcfg.CapabilityAdmin: []tailcfg.RawMessage{},
|
|
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
|
}
|
|
|
|
if cfg.RandomizeClientPort {
|
|
tNode.CapMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
|
}
|
|
|
|
// Set LastSeen only for offline nodes to avoid confusing Tailscale clients
|
|
// during rapid reconnection cycles. Online nodes should not have LastSeen set
|
|
// as this can make clients interpret them as "not online" despite Online=true.
|
|
if node.LastSeen().Valid() && node.IsOnline().Valid() && !node.IsOnline().Get() {
|
|
lastSeen := node.LastSeen().Get()
|
|
tNode.LastSeen = &lastSeen
|
|
}
|
|
|
|
return &tNode, nil
|
|
}
|