Restrict rule authorization to accept rules, the destination side for SSH, and the marker protocol's own port

This commit is contained in:
Viktor Liu
2026-09-22 14:53:37 +02:00
parent 6c15fa1d84
commit 750d093300
6 changed files with 297 additions and 52 deletions
+7 -3
View File
@@ -81,14 +81,18 @@ func MergeWildcardUsers(dst map[string]map[string]struct{}, users map[string]str
// NormalizePolicyRuleProtocol is the real-typed sibling of the twin helper in
// shared types: it maps the NetBird virtual protocols to the wire protocol and
// scopes a portless netbird-vnc rule to the embedded VNC port.
// scopes a portless marker-protocol rule to the port that protocol implies.
func NormalizePolicyRuleProtocol(rule *PolicyRule) (*PolicyRule, PolicyRuleProtocolType) {
protocol := sharedtypes.WirePolicyRuleProtocol(rule.Protocol)
if rule.Protocol != PolicyRuleProtocolNetbirdVNC || len(rule.Ports) > 0 || len(rule.PortRanges) > 0 {
if len(rule.Ports) > 0 || len(rule.PortRanges) > 0 {
return rule, protocol
}
ports, ok := sharedtypes.MarkerScopedPorts(rule.Protocol)
if !ok {
return rule, protocol
}
scoped := *rule
scoped.Ports = sharedtypes.VNCScopedPorts()
scoped.Ports = ports
return &scoped, protocol
}
@@ -1412,3 +1412,38 @@ func TestPeerSSHEnabledFromPolicies_MatchesMap_Sweep(t *testing.T) {
assertSSHEquivalence(t, account, peerID, validatedPeers)
}
}
// The bidirectional shape is the one the three SSH deciders can disagree on:
// Calculate walks ApplyResolvedRuleToState, the components sync path walks
// ruleEnablesSSHForPeer, and the login/register path walks
// PeerSSHEnabledFromPolicies. Every other case in this file sets
// Bidirectional:false or omits it, so none of them exercise the source side.
func TestPeerSSHEnabledFromPolicies_MatchesMap_BidirectionalNetbirdSSH(t *testing.T) {
account, validatedPeers := scalableTestAccountWithoutDefaultPolicy(20, 2)
account.Groups["ssh-users"] = &types.Group{ID: "ssh-users", Name: "SSH Users", Peers: []string{}}
account.Policies = append(account.Policies, &types.Policy{
ID: "policy-ssh-bidi", Name: "SSH Access", Enabled: true, AccountID: "test-account",
Rules: []*types.PolicyRule{{
ID: "rule-ssh-bidi", Name: "Allow SSH", Enabled: true,
Action: types.PolicyTrafficActionAccept, Protocol: types.PolicyRuleProtocolNetbirdSSH,
Bidirectional: true,
Sources: []string{"group-0"}, Destinations: []string{"group-1"},
AuthorizedGroups: map[string][]string{"ssh-users": {"root"}},
}},
})
// peer-0 is source-only, peer-10 is destination-only.
assertSSHEquivalence(t, account, "peer-0", validatedPeers)
assertSSHEquivalence(t, account, "peer-10", validatedPeers)
sourceOnly := componentsNetworkMap(account, "peer-0", validatedPeers)
require.NotNil(t, sourceOnly)
assert.False(t, sourceOnly.EnableSSH,
"a bidirectional netbird-ssh rule must not enable SSH on its source-side peer")
assert.Empty(t, sourceOnly.AuthorizedUsers,
"a source-side peer must carry no authorized users")
destination := componentsNetworkMap(account, "peer-10", validatedPeers)
require.NotNil(t, destination)
assert.True(t, destination.EnableSSH, "the destination-side peer must have SSH enabled")
}