diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go index c5280fd9b..4a237ae12 100644 --- a/client/internal/peer/status.go +++ b/client/internal/peer/status.go @@ -306,13 +306,18 @@ func (d *Status) PeerByIP(ip string) (string, bool) { } // PeerStateByIP returns the full peer State for the given tunnel IP. -// Returns the zero State and false when no peer matches. +// Matches against either the IPv4 (State.IP) or IPv6 (State.IPv6) tunnel +// address so dual-stack peers are reachable on either family. Returns the +// zero State and false when no peer matches or the input is empty. func (d *Status) PeerStateByIP(ip string) (State, bool) { + if ip == "" { + return State{}, false + } d.mux.Lock() defer d.mux.Unlock() for _, state := range d.peers { - if state.IP == ip { + if (state.IP != "" && state.IP == ip) || (state.IPv6 != "" && state.IPv6 == ip) { return state, true } } diff --git a/client/internal/peer/status_test.go b/client/internal/peer/status_test.go index d267eedf8..8d889b0ae 100644 --- a/client/internal/peer/status_test.go +++ b/client/internal/peer/status_test.go @@ -65,18 +65,29 @@ func TestUpdatePeerState(t *testing.T) { func TestStatus_PeerStateByIP(t *testing.T) { status := NewRecorder("https://mgm") - require := assert.New(t) + req := require.New(t) - require.NoError(status.AddPeer("pk-1", "peer-1.netbird", "100.64.0.10", "")) - require.NoError(status.AddPeer("pk-2", "peer-2.netbird", "100.64.0.11", "")) + req.NoError(status.AddPeer("pk-1", "peer-1.netbird", "100.64.0.10", "")) + req.NoError(status.AddPeer("pk-2", "peer-2.netbird", "100.64.0.11", "")) state, ok := status.PeerStateByIP("100.64.0.10") - require.True(ok, "known tunnel IP should resolve to a peer state") - require.Equal("pk-1", state.PubKey, "matching state must carry the right pub key") - require.Equal("peer-1.netbird", state.FQDN, "matching state must carry the right FQDN") + req.True(ok, "known tunnel IP should resolve to a peer state") + req.Equal("pk-1", state.PubKey, "matching state must carry the right pub key") + req.Equal("peer-1.netbird", state.FQDN, "matching state must carry the right FQDN") _, ok = status.PeerStateByIP("100.64.0.99") - require.False(ok, "unknown IP must report ok=false") + req.False(ok, "unknown IP must report ok=false") +} + +func TestStatus_PeerStateByIP_MatchesIPv6(t *testing.T) { + status := NewRecorder("https://mgm") + req := require.New(t) + + req.NoError(status.AddPeer("pk-1", "peer-1.netbird", "100.64.0.10", "fd00::1")) + + state, ok := status.PeerStateByIP("fd00::1") + req.True(ok, "IPv6-only match must resolve to the peer state") + req.Equal("pk-1", state.PubKey, "matching state must carry the right pub key") } func TestStatus_UpdatePeerFQDN(t *testing.T) { diff --git a/management/internals/shared/grpc/proxy.go b/management/internals/shared/grpc/proxy.go index 25bf52d8c..401bbbb80 100644 --- a/management/internals/shared/grpc/proxy.go +++ b/management/internals/shared/grpc/proxy.go @@ -1671,6 +1671,12 @@ func (s *ProxyServiceServer) ValidateTunnelPeer(ctx context.Context, req *proto. }, nil } + // Mirror ValidateSession: account-scoped (BYOP) proxy tokens may only + // validate and mint session cookies for their own account's domains. + if err := enforceAccountScope(ctx, service.AccountID); err != nil { + return nil, err + } + peer, err := s.peersManager.GetPeerByTunnelIP(ctx, service.AccountID, tunnelIP) if err != nil || peer == nil { log.WithFields(log.Fields{"domain": domain, "tunnel_ip": tunnelIPStr}).Debug("ValidateTunnelPeer: peer not found") diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index b9d490dd9..244bda0b5 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -5929,7 +5929,7 @@ func (s *SqlStore) getClusterCapability(ctx context.Context, clusterAddr, column AnyTrue bool } - err := s.db. + err := s.db.WithContext(ctx). Model(&proxy.Proxy{}). Select("COUNT(CASE WHEN "+column+" IS NOT NULL THEN 1 END) > 0 AS has_capability, "+ "COALESCE(MAX(CASE WHEN "+column+" = true THEN 1 ELSE 0 END), 0) = 1 AS any_true").