From 10ba4b368f8f2dfbadd0076d01e00b3851f0e8d1 Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Sat, 16 May 2026 15:49:07 +0200 Subject: [PATCH] Collect SSH authorized users for bidirectional rules on source peers --- management/server/types/account.go | 9 +++++++-- management/server/types/account_components.go | 5 +++++ management/server/types/networkmap_components.go | 9 +++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/management/server/types/account.go b/management/server/types/account.go index 6be865a43..a2f302a99 100644 --- a/management/server/types/account.go +++ b/management/server/types/account.go @@ -1039,7 +1039,12 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P generateResources(rule, sourcePeers, FirewallRuleDirectionIN) } - if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdSSH { + // Auth is collected when this peer serves the rule. For bidirectional + // rules the peer-in-sources side also serves inbound traffic, so it + // must be treated as a destination too. + peerServesAuth := peerInDestinations || (rule.Bidirectional && peerInSources) + + if peerServesAuth && rule.Protocol == PolicyRuleProtocolNetbirdSSH { sshEnabled = true switch { case len(rule.AuthorizedGroups) > 0: @@ -1071,7 +1076,7 @@ func (a *Account) GetPeerConnectionResources(ctx context.Context, peer *nbpeer.P default: authorizedUsers[auth.Wildcard] = a.getAllowedUserIDs() } - } else if peerInDestinations && policyRuleImpliesLegacySSH(rule) && peer.SSHEnabled { + } else if peerServesAuth && policyRuleImpliesLegacySSH(rule) && peer.SSHEnabled { sshEnabled = true authorizedUsers[auth.Wildcard] = a.getAllowedUserIDs() } diff --git a/management/server/types/account_components.go b/management/server/types/account_components.go index a42028351..31183e0f5 100644 --- a/management/server/types/account_components.go +++ b/management/server/types/account_components.go @@ -342,7 +342,12 @@ func (a *Account) getPeersGroupsPoliciesRoutes( for _, srcGroupID := range rule.Sources { relevantGroupIDs[srcGroupID] = a.GetGroup(srcGroupID) } + } + // SSH auth requirements are gathered whenever this peer serves + // the rule. For bidirectional rules the peer-in-sources side + // also serves inbound traffic and must be treated as a destination. + if peerInDestinations || (rule.Bidirectional && peerInSources) { if rule.Protocol == PolicyRuleProtocolNetbirdSSH { switch { case len(rule.AuthorizedGroups) > 0: diff --git a/management/server/types/networkmap_components.go b/management/server/types/networkmap_components.go index a3f2d15e9..5516da314 100644 --- a/management/server/types/networkmap_components.go +++ b/management/server/types/networkmap_components.go @@ -230,7 +230,12 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ( generateResources(rule, sourcePeers, FirewallRuleDirectionIN) } - if peerInDestinations && rule.Protocol == PolicyRuleProtocolNetbirdSSH { + // Auth is collected when this peer serves the rule. For bidirectional + // rules the peer-in-sources side also serves inbound traffic, so it + // must be treated as a destination too. + peerServesAuth := peerInDestinations || (rule.Bidirectional && peerInSources) + + if peerServesAuth && rule.Protocol == PolicyRuleProtocolNetbirdSSH { sshEnabled = true switch { case len(rule.AuthorizedGroups) > 0: @@ -261,7 +266,7 @@ func (c *NetworkMapComponents) getPeerConnectionResources(targetPeerID string) ( default: authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs() } - } else if peerInDestinations && policyRuleImpliesLegacySSH(rule) && targetPeer.SSHEnabled { + } else if peerServesAuth && policyRuleImpliesLegacySSH(rule) && targetPeer.SSHEnabled { sshEnabled = true authorizedUsers[auth.Wildcard] = c.getAllowedUserIDs() }