package grpc import ( "testing" "github.com/stretchr/testify/assert" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/types" ) // TestComputeSSHEnabledForPeer covers both Calculate-mirroring branches: // explicit NetbirdSSH protocol, and the legacy implicit case where a // TCP/22 (or 22022 / ALL / port-range-covering-22) rule activates SSH when // the destination peer has SSHEnabled=true locally. func TestComputeSSHEnabledForPeer(t *testing.T) { const targetPeerID = "target" const targetGroupID = "g_dst" mkComponents := func(rule *types.PolicyRule, sshEnabled bool) (*types.NetworkMapComponents, *nbpeer.Peer) { peer := &nbpeer.Peer{ID: targetPeerID, SSHEnabled: sshEnabled} group := &types.ComponentGroup{ID: targetGroupID, Name: "dst", Peers: []string{targetPeerID}} return &types.NetworkMapComponents{ Peers: map[string]*types.ComponentPeer{targetPeerID: peer.ToComponent()}, Groups: map[string]*types.ComponentGroup{targetGroupID: group}, Policies: []*types.Policy{{ ID: "p", Enabled: true, Rules: []*types.PolicyRule{rule}, }}, }, peer } cases := []struct { name string peerSSH bool rule types.PolicyRule wantEnabled bool }{ { name: "explicit-netbird-ssh-activates-regardless-of-peer-ssh", peerSSH: false, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolNetbirdSSH, Destinations: []string{targetGroupID}, }, wantEnabled: true, }, { name: "implicit-tcp-22-with-peer-ssh", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolTCP, Ports: []string{"22"}, Destinations: []string{targetGroupID}, }, wantEnabled: true, }, { name: "implicit-tcp-22-without-peer-ssh-disabled", peerSSH: false, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolTCP, Ports: []string{"22"}, Destinations: []string{targetGroupID}, }, wantEnabled: false, }, { name: "implicit-tcp-22022-with-peer-ssh", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolTCP, Ports: []string{"22022"}, Destinations: []string{targetGroupID}, }, wantEnabled: true, }, { name: "implicit-all-protocol-with-peer-ssh", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolALL, Destinations: []string{targetGroupID}, }, wantEnabled: true, }, { name: "implicit-port-range-covers-22", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolTCP, PortRanges: []types.RulePortRange{{Start: 20, End: 30}}, Destinations: []string{targetGroupID}, }, wantEnabled: true, }, { name: "tcp-80-no-ssh", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolTCP, Ports: []string{"80"}, Destinations: []string{targetGroupID}, }, wantEnabled: false, }, { name: "disabled-rule-skipped", peerSSH: true, rule: types.PolicyRule{ Enabled: false, Protocol: types.PolicyRuleProtocolNetbirdSSH, Destinations: []string{targetGroupID}, }, wantEnabled: false, }, { name: "peer-not-in-destinations", peerSSH: true, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolNetbirdSSH, Destinations: []string{"g_other"}, // target not in this group }, wantEnabled: false, }, { name: "peer-typed-destination-resource-matches", peerSSH: false, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolNetbirdSSH, DestinationResource: types.Resource{ID: targetPeerID, Type: types.ResourceTypePeer}, }, wantEnabled: true, }, { name: "non-peer-destination-resource-falls-through-to-groups", peerSSH: false, rule: types.PolicyRule{ Enabled: true, Protocol: types.PolicyRuleProtocolNetbirdSSH, DestinationResource: types.Resource{ID: targetPeerID, Type: "host"}, // wrong type Destinations: []string{targetGroupID}, // saved by group fallback }, wantEnabled: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { c, peer := mkComponents(&tc.rule, tc.peerSSH) got := computeSSHEnabledForPeer(c, peer) assert.Equal(t, tc.wantEnabled, got) }) } } // TestComputeSSHEnabledForPeer_TargetMissingFromComponents covers the // belt-and-suspenders presence guard mirroring Calculate's // getAllPeersFromGroups invariant. func TestComputeSSHEnabledForPeer_TargetMissingFromComponents(t *testing.T) { peer := &nbpeer.Peer{ID: "missing", SSHEnabled: true} c := &types.NetworkMapComponents{ Peers: map[string]*types.ComponentPeer{}, // target peer NOT present Groups: map[string]*types.ComponentGroup{ "g": {ID: "g", Peers: []string{"missing"}}, }, Policies: []*types.Policy{{ ID: "p", Enabled: true, Rules: []*types.PolicyRule{{ Enabled: true, Protocol: types.PolicyRuleProtocolNetbirdSSH, Destinations: []string{"g"}, }}, }}, } assert.False(t, computeSSHEnabledForPeer(c, peer), "missing target peer must short-circuit to false, not consult policies") } // TestComputeSSHEnabledForPeer_NilInputs guards the cheap nil-checks at // function entry — Calculate doesn't accept nil either, but the helper is // exported indirectly via ToComponentSyncResponse and may receive nil // components on graceful-degrade paths. func TestComputeSSHEnabledForPeer_NilInputs(t *testing.T) { assert.False(t, computeSSHEnabledForPeer(nil, &nbpeer.Peer{ID: "x"})) assert.False(t, computeSSHEnabledForPeer(&types.NetworkMapComponents{}, nil)) }