mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-04 13:53:37 +09:00
+11
-67
@@ -14,7 +14,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/policy"
|
||||
"github.com/juanfont/headscale/hscontrol/state"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/types/change"
|
||||
@@ -412,7 +411,7 @@ func (m *mapper) buildFromChange(
|
||||
} else {
|
||||
if len(resp.PeersChanged) > 0 {
|
||||
peers := m.state.ListPeers(nodeID, resp.PeersChanged...)
|
||||
builder.WithUserProfiles(m.filterVisibleNodes(nodeID, peers))
|
||||
builder.WithUserProfiles(peers)
|
||||
builder.WithPeerChanges(peers)
|
||||
}
|
||||
|
||||
@@ -433,48 +432,9 @@ func (m *mapper) buildFromChange(
|
||||
return builder.Build()
|
||||
}
|
||||
|
||||
// visiblePeerIDs returns the set of peer node IDs the recipient may see under
|
||||
// the current policy. It is the single visibility decision shared by the
|
||||
// incremental peer-change and user-profile paths, computed from the same live
|
||||
// per-node matchers and [policy.ReduceNodes] filter that
|
||||
// [MapResponseBuilder.buildTailPeers] applies to full peer objects, so the
|
||||
// paths cannot drift. The recipient's adjacency from the NodeStore
|
||||
// ([NodeStore.ListPeers]) is the authority for which peers exist for it; the
|
||||
// live matchers only narrow that set further, matching buildTailPeers.
|
||||
//
|
||||
// ok is false when the node or its matchers cannot be resolved; callers must
|
||||
// then fail closed (emit nothing) rather than risk leaking forbidden peers.
|
||||
func (m *mapper) visiblePeerIDs(nodeID types.NodeID) (map[tailcfg.NodeID]struct{}, bool) {
|
||||
node, ok := m.state.GetNodeByID(nodeID)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
matchers, err := m.state.MatchersForNode(node)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
peers := m.state.ListPeers(nodeID)
|
||||
|
||||
// No matchers means no policy restrictions, so every peer is visible —
|
||||
// the same default buildTailPeers applies.
|
||||
if len(matchers) > 0 {
|
||||
peers = policy.ReduceNodes(node, peers, matchers)
|
||||
}
|
||||
|
||||
// Key by tailcfg.NodeID so the peer-patch path can look up by patch.NodeID
|
||||
// directly, avoiding an unchecked int64->uint64 conversion.
|
||||
visible := make(map[tailcfg.NodeID]struct{}, peers.Len())
|
||||
for _, peer := range peers.All() {
|
||||
visible[peer.ID().NodeID()] = struct{}{}
|
||||
}
|
||||
|
||||
return visible, true
|
||||
}
|
||||
|
||||
// filterVisiblePeerPatches drops peer-change patches whose target peer the
|
||||
// recipient cannot see under the ACL policy. Without it, online/offline,
|
||||
// filterVisiblePeerPatches drops peer-change patches whose target is not in
|
||||
// the recipient's NodeStore peer map, the same set
|
||||
// [MapResponseBuilder.buildTailPeers] is fed from. Without it, online/offline,
|
||||
// endpoint, and key-expiry patches disclose the existence, presence, and
|
||||
// addresses of peers the recipient's policy forbids it from accessing.
|
||||
func (m *mapper) filterVisiblePeerPatches(
|
||||
@@ -485,10 +445,13 @@ func (m *mapper) filterVisiblePeerPatches(
|
||||
return patches
|
||||
}
|
||||
|
||||
visible, ok := m.visiblePeerIDs(nodeID)
|
||||
if !ok {
|
||||
// Fail closed: if visibility cannot be resolved, send no patches.
|
||||
return nil
|
||||
// Key by tailcfg.NodeID so patches are looked up by patch.NodeID
|
||||
// directly, avoiding an unchecked int64->uint64 conversion.
|
||||
peers := m.state.ListPeers(nodeID)
|
||||
|
||||
visible := make(map[tailcfg.NodeID]struct{}, peers.Len())
|
||||
for _, peer := range peers.All() {
|
||||
visible[peer.ID().NodeID()] = struct{}{}
|
||||
}
|
||||
|
||||
return filterByVisible(visible, patches, func(p *tailcfg.PeerChange) tailcfg.NodeID {
|
||||
@@ -496,25 +459,6 @@ func (m *mapper) filterVisiblePeerPatches(
|
||||
})
|
||||
}
|
||||
|
||||
// filterVisibleNodes restricts a peer slice to the nodes the recipient can see
|
||||
// under the ACL policy. It guards UserProfiles on the incremental PeersChanged
|
||||
// path, which receives an unfiltered node slice and would otherwise leak the
|
||||
// identities of users whose nodes the recipient cannot access.
|
||||
func (m *mapper) filterVisibleNodes(
|
||||
nodeID types.NodeID,
|
||||
peers views.Slice[types.NodeView],
|
||||
) views.Slice[types.NodeView] {
|
||||
visible, ok := m.visiblePeerIDs(nodeID)
|
||||
if !ok {
|
||||
// Fail closed: emit no peer user profiles rather than risk a leak.
|
||||
return views.SliceOf([]types.NodeView{})
|
||||
}
|
||||
|
||||
return views.SliceOf(filterByVisible(visible, peers.AsSlice(), func(p types.NodeView) tailcfg.NodeID {
|
||||
return p.ID().NodeID()
|
||||
}))
|
||||
}
|
||||
|
||||
// filterByVisible keeps only the items whose key resolves to a NodeID present
|
||||
// in the visible set, preserving input order.
|
||||
func filterByVisible[T any](
|
||||
|
||||
Reference in New Issue
Block a user