Skip to content

[management] allow local routing peer resource#5814

Open
pascal-fischer wants to merge 11 commits intomainfrom
feature/allow-local-routing-peer-resource
Open

[management] allow local routing peer resource#5814
pascal-fischer wants to merge 11 commits intomainfrom
feature/allow-local-routing-peer-resource

Conversation

@pascal-fischer
Copy link
Copy Markdown
Collaborator

@pascal-fischer pascal-fischer commented Apr 7, 2026

Describe your changes

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features
    • API accepts an optional "on_routing_peer" flag for network resources; it's persisted and returned in responses.
    • Router deployments now generate local inbound firewall rules for resources marked as routing-peer, improving routing and access control.
  • Tests
    • Unit test updated to reflect the new routing-peer behavior during resource creation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added an optional OnRoutingPeer boolean to network resources, persisted and exposed via API; resource construction and SQL scanning pass this flag through, and network-map logic generates additional local inbound firewall rules when a node is a routing peer with OnRoutingPeer enabled.

Changes

Cohort / File(s) Summary
Database & Persistence
management/server/store/sql_store.go, management/server/store/sql_store_test.go
SELECT now reads on_routing_peer into resources; tests adjusted for new NewNetworkResource parameter ordering.
Resource Manager
management/server/networks/resources/manager.go
CreateResource now forwards resource.OnRoutingPeer into types.NewNetworkResource.
Resource Type Definitions
management/server/networks/resources/types/resource.go
Added OnRoutingPeer field; updated NewNetworkResource, Copy, ToAPIResponse, and FromAPIRequest to handle the field.
API Schemas & Generated Types
shared/management/http/api/openapi.yml, shared/management/http/api/types.gen.go
Added optional on_routing_peer (*bool) to NetworkResource, NetworkResourceMinimum, and NetworkResourceRequest schemas/types.
Network Map / Routing Logic
management/server/types/networkmap_components.go
Calculate and getNetworkResourcesRoutesToSync extended to return local inbound firewall rules when isRoutingPeer and OnRoutingPeer are enabled; added getLocalResourceFirewallRules helper and integrated results into firewall rules aggregation.

Sequence Diagram(s)

sequenceDiagram
    participant API as API / Manager
    participant Store as Store (SQL)
    participant Map as NetworkMapComponents
    participant FW as FirewallRuleBuilder

    API->>Store: fetch network resources (includes on_routing_peer)
    Store-->>API: resources (OnRoutingPeer set)
    API->>Map: Calculate(network, resources, isRouter/isRoutingPeer)
    Map->>FW: getNetworkResourcesRoutesToSync(resources, isRoutingPeer)
    FW-->>Map: routes + localResourceFwRules (when isRoutingPeer && OnRoutingPeer)
    Map-->>API: RoutesFirewallRules (includes local inbound rules)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • crn4

Poem

🐰 I nibble flags in fields so small,
A hop, a scan, a routing call.
If peers may route, a doorway shows,
Local rules sprout where the traffic goes.
Hop, test, merge — the network grows! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks critical implementation details: no explanation of what changes were made, no issue ticket link, no stack information, and no description of the feature itself. Add a detailed explanation of the changes made, reference the issue ticket, provide stack information if applicable, and explain why documentation is not needed for this feature enhancement.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: adding support for local routing peer resources, which aligns with the changeset that adds an OnRoutingPeer field across multiple components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/allow-local-routing-peer-resource

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
management/server/store/sql_store.go (1)

2262-2279: ⚠️ Potential issue | 🔴 Critical

Add schema migration for on_routing_peer column

The query on line 2262 selects on_routing_peer from the database, but there is no corresponding migration in management/server/store/store.go to add this column to existing databases. Databases that haven't been migrated will fail at runtime when getNetworkResources() is called.

🔧 Proposed fix
--- a/management/server/store/store.go
+++ b/management/server/store/store.go
@@ -1,7 +1,10 @@
 		func(db *gorm.DB) error {
 			return migration.MigrateNewField[resourceTypes.NetworkResource](ctx, db, "enabled", true)
 		},
+		func(db *gorm.DB) error {
+			return migration.MigrateNewField[resourceTypes.NetworkResource](ctx, db, "on_routing_peer", false)
+		},
 		func(db *gorm.DB) error {
 			return migration.MigrateNewField[routerTypes.NetworkRouter](ctx, db, "enabled", true)
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/store/sql_store.go` around lines 2262 - 2279, The query in
getNetworkResources reads the on_routing_peer column but no migration adds that
column; add a schema migration that alters network_resources to include
on_routing_peer boolean NOT NULL DEFAULT false (or NULLABLE if intended) and
apply it in the migrations list used by store initialization (update the
migrations slice in store.go where migrations are registered/defined) so
existing DBs get the new column before getNetworkResources() is used; ensure the
migration has a unique name/version consistent with the project's migration
convention.
management/server/types/networkmap_components.go (1)

708-720: ⚠️ Potential issue | 🔴 Critical

Keep the return-level isRoutingPeer separate from the current resource check.

Line 718 keys local peer rules off isRoutingPeer, but that flag stays true after the first matching resource. A peer that routes network A but not network B will still pick up B's OnRoutingPeer FirewallRules later in the loop, which can open the router itself for the wrong resource because these rules are not destination-scoped.

🐛 Suggested fix
-		var addSourcePeers bool
+		var (
+			addSourcePeers       bool
+			isCurrentRoutingPeer bool
+		)

 		networkRoutingPeers, exists := c.RoutersMap[resource.NetworkID]
 		if exists {
 			if router, ok := networkRoutingPeers[peerID]; ok {
-				isRoutingPeer, addSourcePeers = true, true
+				isRoutingPeer = true
+				addSourcePeers = true
+				isCurrentRoutingPeer = true
 				routes = append(routes, c.getNetworkResourcesRoutes(resource, peerID, router)...)
 			}
 		}

 		addedResourceRoute := false
 		for _, policy := range c.ResourcePoliciesMap[resource.ID] {
-			if isRoutingPeer && resource.OnRoutingPeer {
+			if isCurrentRoutingPeer && resource.OnRoutingPeer {
 				localResourceFwRule = append(localResourceFwRule, c.getLocalResourceFirewallRules(policy)...)
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 708 - 720, The
loop reuses the outer isRoutingPeer flag across resources causing later
resources to incorrectly get OnRoutingPeer firewall rules; change to compute a
per-resource boolean (e.g., isRoutingPeerForResource) by checking
c.RoutersMap[resource.NetworkID] and networkRoutingPeers[peerID] for the current
resource, use that per-resource flag when deciding to append
getLocalResourceFirewallRules(policy) and when calling
getNetworkResourcesRoutes, but still set the outer/addSourcePeers
(addSourcePeers or other summary flags) only when a match is first found so
cumulative state is preserved.
🧹 Nitpick comments (1)
management/server/networks/resources/types/resource.go (1)

46-46: Avoid adjacent boolean parameters in NewNetworkResource.

The onRoutingPeer, enabled bool tail is easy to swap at call sites and reduces readability. Prefer a params/options struct to make calls self-documenting and safer.

♻️ Proposed refactor
+type NewNetworkResourceParams struct {
+	AccountID     string
+	NetworkID     string
+	Name          string
+	Description   string
+	Address       string
+	GroupIDs      []string
+	OnRoutingPeer bool
+	Enabled       bool
+}
+
-func NewNetworkResource(accountID, networkID, name, description, address string, groupIDs []string, onRoutingPeer, enabled bool) (*NetworkResource, error) {
-	resourceType, domain, prefix, err := GetResourceType(address)
+func NewNetworkResource(p NewNetworkResourceParams) (*NetworkResource, error) {
+	resourceType, domain, prefix, err := GetResourceType(p.Address)
 	if err != nil {
 		return nil, fmt.Errorf("invalid address: %w", err)
 	}
 
 	return &NetworkResource{
 		ID:            xid.New().String(),
-		AccountID:     accountID,
-		NetworkID:     networkID,
-		Name:          name,
-		Description:   description,
+		AccountID:     p.AccountID,
+		NetworkID:     p.NetworkID,
+		Name:          p.Name,
+		Description:   p.Description,
 		Type:          resourceType,
-		Address:       address,
+		Address:       p.Address,
 		Domain:        domain,
 		Prefix:        prefix,
-		GroupIDs:      groupIDs,
-		Enabled:       enabled,
-		OnRoutingPeer: onRoutingPeer,
+		GroupIDs:      p.GroupIDs,
+		Enabled:       p.Enabled,
+		OnRoutingPeer: p.OnRoutingPeer,
 	}, nil
 }
#!/bin/bash
# Verify all current call sites and inspect bool argument ordering.
# Expected: every invocation clearly passes onRoutingPeer first, enabled second.
rg -nP --type=go '\bNewNetworkResource\s*\(' -C 3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/networks/resources/types/resource.go` at line 46, Replace
the adjacent boolean parameters on NewNetworkResource by introducing an options
struct (e.g., type NetworkResourceOptions with fields OnRoutingPeer bool and
Enabled bool), change NewNetworkResource signature to accept opts
*NetworkResourceOptions (or opts NetworkResourceOptions), update the function
body to read opts.OnRoutingPeer and opts.Enabled, and then update every call
site of NewNetworkResource to pass a named options struct (so callers use
NetworkResourceOptions{OnRoutingPeer: true, Enabled: false} etc.); also update
any tests and docs referring to the old parameter order and run a project-wide
search for NewNetworkResource to fix all invocations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@management/server/types/networkmap_components.go`:
- Around line 746-799: getLocalResourceFirewallRules currently uses
getPoliciesSourcePeers(policy) to build a union of all policy sources, causing
peers from one rule to inherit other rules' ports/protocols and ignoring
per-rule SourcePostureChecks; change the function to build rules from each
Rule's own sources (e.g., call a per-rule source resolver instead of
getPoliciesSourcePeers) so you iterate rule.SourcePeers/SourcePostureChecks for
each Policy.Rules entry, consult GetPeerInfo(peerID) for each of those peerIDs,
and emit firewall rules only for that rule's peers; update the caller (as
suggested) to pass peerID into getLocalResourceFirewallRules if needed so the
function can validate posture checks per-peer.

---

Outside diff comments:
In `@management/server/store/sql_store.go`:
- Around line 2262-2279: The query in getNetworkResources reads the
on_routing_peer column but no migration adds that column; add a schema migration
that alters network_resources to include on_routing_peer boolean NOT NULL
DEFAULT false (or NULLABLE if intended) and apply it in the migrations list used
by store initialization (update the migrations slice in store.go where
migrations are registered/defined) so existing DBs get the new column before
getNetworkResources() is used; ensure the migration has a unique name/version
consistent with the project's migration convention.

In `@management/server/types/networkmap_components.go`:
- Around line 708-720: The loop reuses the outer isRoutingPeer flag across
resources causing later resources to incorrectly get OnRoutingPeer firewall
rules; change to compute a per-resource boolean (e.g., isRoutingPeerForResource)
by checking c.RoutersMap[resource.NetworkID] and networkRoutingPeers[peerID] for
the current resource, use that per-resource flag when deciding to append
getLocalResourceFirewallRules(policy) and when calling
getNetworkResourcesRoutes, but still set the outer/addSourcePeers
(addSourcePeers or other summary flags) only when a match is first found so
cumulative state is preserved.

---

Nitpick comments:
In `@management/server/networks/resources/types/resource.go`:
- Line 46: Replace the adjacent boolean parameters on NewNetworkResource by
introducing an options struct (e.g., type NetworkResourceOptions with fields
OnRoutingPeer bool and Enabled bool), change NewNetworkResource signature to
accept opts *NetworkResourceOptions (or opts NetworkResourceOptions), update the
function body to read opts.OnRoutingPeer and opts.Enabled, and then update every
call site of NewNetworkResource to pass a named options struct (so callers use
NetworkResourceOptions{OnRoutingPeer: true, Enabled: false} etc.); also update
any tests and docs referring to the old parameter order and run a project-wide
search for NewNetworkResource to fix all invocations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56bdbac6-ebe7-4153-94d5-305478b0f208

📥 Commits

Reviewing files that changed from the base of the PR and between 435203b and f741777.

📒 Files selected for processing (6)
  • management/server/networks/resources/manager.go
  • management/server/networks/resources/types/resource.go
  • management/server/store/sql_store.go
  • management/server/types/networkmap_components.go
  • shared/management/http/api/openapi.yml
  • shared/management/http/api/types.gen.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
management/server/store/sql_store_test.go (1)

2466-2481: ⚠️ Potential issue | 🟡 Minor

Assert OnRoutingPeer and Enabled to actually validate the new field round-trip

At Line 2466, the constructor now sets onRoutingPeer=false and enabled=true, but the test never asserts those persisted values after reload. This can miss regressions in SQL mapping for the new feature.

✅ Suggested test assertions
  savedNetResource, err := store.GetNetworkResourceByID(context.Background(), LockingStrengthNone, accountID, netResource.ID)
  require.NoError(t, err)
  require.Equal(t, netResource.ID, savedNetResource.ID)
  require.Equal(t, netResource.Name, savedNetResource.Name)
  require.Equal(t, netResource.NetworkID, savedNetResource.NetworkID)
  require.Equal(t, netResource.Type, resourceTypes.NetworkResourceType("domain"))
  require.Equal(t, netResource.Domain, "example.com")
  require.Equal(t, netResource.AccountID, savedNetResource.AccountID)
  require.Equal(t, netResource.Prefix, netip.Prefix{})
+ require.Equal(t, netResource.OnRoutingPeer, savedNetResource.OnRoutingPeer)
+ require.Equal(t, netResource.Enabled, savedNetResource.Enabled)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/store/sql_store_test.go` around lines 2466 - 2481, The test
creates a NetworkResource via resourceTypes.NewNetworkResource with
onRoutingPeer=false and enabled=true but never asserts those fields after
reload; after calling store.GetNetworkResourceByID (and alongside the existing
require.Equal checks), add assertions that savedNetResource.OnRoutingPeer equals
false and savedNetResource.Enabled equals true to validate the SQL round-trip
for those new fields (references: NewNetworkResource, netResource,
SaveNetworkResource, GetNetworkResourceByID, savedNetResource.OnRoutingPeer,
savedNetResource.Enabled).
♻️ Duplicate comments (1)
management/server/types/networkmap_components.go (1)

747-752: ⚠️ Potential issue | 🔴 Critical

Resolve local-resource sources per rule, not per policy.

Line 747 builds a policy-wide source union once, and Lines 755-800 then emit every enabled rule for every peer in that union. If sibling rules have different sources, a peer that only matches one rule can inherit the others' ports/protocols here. Build the peer set inside the rule loop instead of reusing a policy-wide list. The call at Line 719 will also need peerID threaded through.

🔒 Suggested direction
-func (c *NetworkMapComponents) getLocalResourceFirewallRules(policy *Policy) []*FirewallRule {
-	sourcePeerIDs := c.getPoliciesSourcePeers([]*Policy{policy})
-
-	postureValidatedPeerIDs := make([]string, 0)
-	for _, pID := range c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks) {
-		postureValidatedPeerIDs = append(postureValidatedPeerIDs, pID)
-	}
-
+func (c *NetworkMapComponents) getLocalResourceFirewallRules(targetPeerID string, policy *Policy) []*FirewallRule {
+	distributionPeers := c.getPoliciesSourcePeers([]*Policy{policy})
 	rules := make([]*FirewallRule, 0)
 	for _, rule := range policy.Rules {
 		if !rule.Enabled {
 			continue
 		}
@@
-		for _, peerID := range postureValidatedPeerIDs {
-			peer := c.GetPeerInfo(peerID)
-			if peer == nil {
-				continue
-			}
+		for _, peer := range c.getRulePeers(rule, policy.SourcePostureChecks, targetPeerID, distributionPeers) {
 			peerIP := peer.IP.String()
 			fr := FirewallRule{

Also applies to: 755-800

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 747 - 752, The
code currently computes sourcePeerIDs once per policy (variable sourcePeerIDs
from c.getPoliciesSourcePeers) and then emits every enabled rule for every peer
in that union, allowing peers that match only one sibling rule to inherit
others' ports/protocols; fix by moving the source resolution into the per-rule
loop so each Rule computes its own sourcePeerIDs (call c.getPoliciesSourcePeers
or equivalent with the rule context) and then compute postureValidatedPeerIDs
from c.getPostureValidPeers(...) for that rule only; also thread the peerID
parameter through the earlier helper call site (the existing
getPoliciesSourcePeers/getPostureValidPeers usages) so the per-rule source
resolution can filter correctly and avoid reusing the policy-wide list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@management/server/types/networkmap_components.go`:
- Around line 718-720: The branch that appends local firewall rules is
incorrectly gated by the outer isRoutingPeer flag so once a peer routes any
network all subsequent resources with resource.OnRoutingPeer from other networks
emit rules; change the condition to check the per-resource routing flag instead
(use addSourcePeers or introduce an isResourceRoutingPeer boolean tied to the
specific resource) so only resources for which this peer actually routes will
call c.getLocalResourceFirewallRules and append to localResourceFwRule; update
the conditional that surrounds c.getLocalResourceFirewallRules(policy) to
reference that per-resource flag rather than isRoutingPeer.

---

Outside diff comments:
In `@management/server/store/sql_store_test.go`:
- Around line 2466-2481: The test creates a NetworkResource via
resourceTypes.NewNetworkResource with onRoutingPeer=false and enabled=true but
never asserts those fields after reload; after calling
store.GetNetworkResourceByID (and alongside the existing require.Equal checks),
add assertions that savedNetResource.OnRoutingPeer equals false and
savedNetResource.Enabled equals true to validate the SQL round-trip for those
new fields (references: NewNetworkResource, netResource, SaveNetworkResource,
GetNetworkResourceByID, savedNetResource.OnRoutingPeer,
savedNetResource.Enabled).

---

Duplicate comments:
In `@management/server/types/networkmap_components.go`:
- Around line 747-752: The code currently computes sourcePeerIDs once per policy
(variable sourcePeerIDs from c.getPoliciesSourcePeers) and then emits every
enabled rule for every peer in that union, allowing peers that match only one
sibling rule to inherit others' ports/protocols; fix by moving the source
resolution into the per-rule loop so each Rule computes its own sourcePeerIDs
(call c.getPoliciesSourcePeers or equivalent with the rule context) and then
compute postureValidatedPeerIDs from c.getPostureValidPeers(...) for that rule
only; also thread the peerID parameter through the earlier helper call site (the
existing getPoliciesSourcePeers/getPostureValidPeers usages) so the per-rule
source resolution can filter correctly and avoid reusing the policy-wide list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3e8bf97-c62b-4c9c-8bf8-0324eeed7ae9

📥 Commits

Reviewing files that changed from the base of the PR and between f741777 and 54a03d7.

📒 Files selected for processing (2)
  • management/server/store/sql_store_test.go
  • management/server/types/networkmap_components.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
management/server/types/networkmap_components.go (2)

718-720: ⚠️ Potential issue | 🔴 Critical

Use addSourcePeers to scope local firewall rules to the current resource.

isRoutingPeer is set once and persists across all resource iterations. After routing one network, any subsequent resource with OnRoutingPeer=true from a different network will incorrectly trigger firewall rule generation even though this peer is not a router for that resource's network.

🔧 Proposed fix
-			if isRoutingPeer && resource.OnRoutingPeer {
+			if addSourcePeers && resource.OnRoutingPeer {
 				localResourceFwRule = append(localResourceFwRule, c.getLocalResourceFirewallRules(policy)...)
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 718 - 720,
isRoutingPeer is being reused across resource iterations, causing rules to be
generated for resources that aren't routers; instead, scope rule generation to
the current resource by using addSourcePeers for that resource. Update the logic
around getLocalResourceFirewallRules so you only append rules when
addSourcePeers(resource) indicates this resource's peers are routing (or pass
the result of addSourcePeers(resource) into getLocalResourceFirewallRules to
produce scoped rules), and remove reliance on the persistent isRoutingPeer flag
so each resource decision is evaluated per-resource.

746-804: ⚠️ Potential issue | 🟠 Major

Build firewall rules from each rule's own sources, not a policy-wide union.

getPoliciesSourcePeers (line 747) collects sources from all rules in the policy. The loop then emits firewall rules for every peer against every enabled rule's ports/protocols. This allows a peer from one rule to inherit the ports/protocols of another rule.

Additionally, consider extracting parts of this logic (e.g., rule-to-firewall conversion) into helper functions to reduce cognitive complexity (currently 25, limit is 20).

🔧 Suggested direction
 func (c *NetworkMapComponents) getLocalResourceFirewallRules(policy *Policy) []*FirewallRule {
-	sourcePeerIDs := c.getPoliciesSourcePeers([]*Policy{policy})
-
-	postureValidatedPeerIDs := make([]string, 0)
-	for _, pID := range c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks) {
-		postureValidatedPeerIDs = append(postureValidatedPeerIDs, pID)
-	}
-
 	rules := make([]*FirewallRule, 0)
 	for _, rule := range policy.Rules {
 		if !rule.Enabled {
 			continue
 		}

 		protocol := rule.Protocol
 		if protocol == PolicyRuleProtocolNetbirdSSH {
 			continue
 		}

-		for _, peerID := range postureValidatedPeerIDs {
+		// Get sources for THIS rule only
+		ruleSources := c.getRuleSourcePeerIDs(rule)
+		validPeers := c.getPostureValidPeers(ruleSources, policy.SourcePostureChecks)
+
+		for _, peerID := range validPeers {
 			peer := c.GetPeerInfo(peerID)

You'd need a helper like getRuleSourcePeerIDs(rule) that extracts peer IDs from rule.Sources groups and rule.SourceResource, similar to how getRulePeers works but returning just IDs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 746 - 804, The
current getLocalResourceFirewallRules uses getPoliciesSourcePeers (policy-wide
union) so peers from one rule get applied to other rules; instead, for each rule
call a new helper getRuleSourcePeerIDs(rule) that extracts peer IDs from
rule.Sources and rule.SourceResource (similar to getRulePeers but returning
IDs), pass those IDs into c.getPostureValidPeers(...) to get
postureValidatedPeerIDs per rule, and then convert that single rule+peer set
into FirewallRule entries; also extract the rule->firewall conversion into a
small helper (e.g., buildFirewallRulesForRuleAndPeer(rule, peerIP)) to lower
cognitive complexity and reuse within getLocalResourceFirewallRules.
🧹 Nitpick comments (2)
management/server/types/networkmap_components.go (2)

695-744: Consider extracting sub-loops to reduce cognitive complexity.

SonarCloud flags this method at complexity 35 (limit 20). The nested loops and conditionals for route generation, source peer collection, and local firewall rules contribute to this. While functional, extracting discrete concerns (e.g., a helper for iterating resources and building local rules) would improve maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 695 - 744, The
function getNetworkResourcesRoutesToSync is too complex (nested
loops/conditionals) — extract responsibilities into small helpers: create a
processResource(resource *NetworkResource, peerID string) that handles
per-resource logic, a handlePolicy(policy *ResourcePolicy, resource
*NetworkResource, peerID string) to encapsulate the inner policy loop (including
addedResourceRoute logic), a collectPeers(peers []string, policy
*ResourcePolicy, addSourcePeers bool) to populate allSourcePeers and call
getPostureValidPeers/ValidatePostureChecksOnPeer, and a
buildLocalFirewallRules(policy *ResourcePolicy) to return localResourceFwRule
entries; then have getNetworkResourcesRoutesToSync iterate NetworkResources and
call these helpers, updating isRoutingPeer, routes (via
getNetworkResourcesRoutes), allSourcePeers and localResourceFwRule accordingly
to reduce cognitive complexity and keep original behavior.

749-752: Simplify loop per staticcheck.

The loop can be replaced with a single append statement.

♻️ Proposed simplification
-	postureValidatedPeerIDs := make([]string, 0)
-	for _, pID := range c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks) {
-		postureValidatedPeerIDs = append(postureValidatedPeerIDs, pID)
-	}
+	postureValidatedPeerIDs := c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 749 - 752, The
manual loop that builds postureValidatedPeerIDs from c.getPostureValidPeers can
be simplified to a single append call: replace the loop that iterates over
c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks) and
appends each pID into postureValidatedPeerIDs with a single append that expands
the returned slice (use append(..., returnedSlice...)) so
postureValidatedPeerIDs is created from the result of c.getPostureValidPeers in
one statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@management/server/types/networkmap_components.go`:
- Around line 718-720: isRoutingPeer is being reused across resource iterations,
causing rules to be generated for resources that aren't routers; instead, scope
rule generation to the current resource by using addSourcePeers for that
resource. Update the logic around getLocalResourceFirewallRules so you only
append rules when addSourcePeers(resource) indicates this resource's peers are
routing (or pass the result of addSourcePeers(resource) into
getLocalResourceFirewallRules to produce scoped rules), and remove reliance on
the persistent isRoutingPeer flag so each resource decision is evaluated
per-resource.
- Around line 746-804: The current getLocalResourceFirewallRules uses
getPoliciesSourcePeers (policy-wide union) so peers from one rule get applied to
other rules; instead, for each rule call a new helper getRuleSourcePeerIDs(rule)
that extracts peer IDs from rule.Sources and rule.SourceResource (similar to
getRulePeers but returning IDs), pass those IDs into c.getPostureValidPeers(...)
to get postureValidatedPeerIDs per rule, and then convert that single rule+peer
set into FirewallRule entries; also extract the rule->firewall conversion into a
small helper (e.g., buildFirewallRulesForRuleAndPeer(rule, peerIP)) to lower
cognitive complexity and reuse within getLocalResourceFirewallRules.

---

Nitpick comments:
In `@management/server/types/networkmap_components.go`:
- Around line 695-744: The function getNetworkResourcesRoutesToSync is too
complex (nested loops/conditionals) — extract responsibilities into small
helpers: create a processResource(resource *NetworkResource, peerID string) that
handles per-resource logic, a handlePolicy(policy *ResourcePolicy, resource
*NetworkResource, peerID string) to encapsulate the inner policy loop (including
addedResourceRoute logic), a collectPeers(peers []string, policy
*ResourcePolicy, addSourcePeers bool) to populate allSourcePeers and call
getPostureValidPeers/ValidatePostureChecksOnPeer, and a
buildLocalFirewallRules(policy *ResourcePolicy) to return localResourceFwRule
entries; then have getNetworkResourcesRoutesToSync iterate NetworkResources and
call these helpers, updating isRoutingPeer, routes (via
getNetworkResourcesRoutes), allSourcePeers and localResourceFwRule accordingly
to reduce cognitive complexity and keep original behavior.
- Around line 749-752: The manual loop that builds postureValidatedPeerIDs from
c.getPostureValidPeers can be simplified to a single append call: replace the
loop that iterates over c.getPostureValidPeers(maps.Keys(sourcePeerIDs),
policy.SourcePostureChecks) and appends each pID into postureValidatedPeerIDs
with a single append that expands the returned slice (use append(...,
returnedSlice...)) so postureValidatedPeerIDs is created from the result of
c.getPostureValidPeers in one statement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11478b43-1e4b-4937-8227-470d1ca28118

📥 Commits

Reviewing files that changed from the base of the PR and between 54a03d7 and 5483a7c.

📒 Files selected for processing (1)
  • management/server/types/networkmap_components.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
management/server/types/networkmap_components.go (2)

718-720: ⚠️ Potential issue | 🔴 Critical

Scope OnRoutingPeer rules to the current resource.

Line 718 still keys this branch off the function-level isRoutingPeer. Once an earlier resource sets that flag, later resource.OnRoutingPeer resources will append local rules even when peerID is not a router for that network, which over-broadens inbound access.

🔧 Suggested fix
-			if isRoutingPeer && resource.OnRoutingPeer {
+			if addSourcePeers && resource.OnRoutingPeer {
 				localResourceFwRule = append(localResourceFwRule, c.getLocalResourceFirewallRules(policy)...)
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 718 - 720, The
branch uses the function-level isRoutingPeer flag which can remain true after an
earlier resource sets it, causing later resources with resource.OnRoutingPeer to
incorrectly append rules; change the condition so it checks routing status for
the current resource only (e.g., compute a per-resource boolean like
isRoutingPeerForResource by evaluating the peerID against this resource's
routing/network attributes, then use if isRoutingPeerForResource &&
resource.OnRoutingPeer { localResourceFwRule = append(localResourceFwRule,
c.getLocalResourceFirewallRules(policy)...) }), ensuring
getLocalResourceFirewallRules is only called when the peer actually routes for
that specific resource.

746-799: ⚠️ Potential issue | 🔴 Critical

Build local resource rules from each rule’s own sources.

Lines 747-748 compute one source-peer union for the whole policy, and Lines 761-796 then emit every enabled rule for every peer in that union. That lets a peer from one sibling rule — or from a disabled sibling, since the union is built before the rule.Enabled check — inherit another rule's ports/protocols.

🔧 Suggested direction
-func (c *NetworkMapComponents) getLocalResourceFirewallRules(policy *Policy) []*FirewallRule {
-	sourcePeerIDs := c.getPoliciesSourcePeers([]*Policy{policy})
-	postureValidatedPeerIDs := c.getPostureValidPeers(maps.Keys(sourcePeerIDs), policy.SourcePostureChecks)
+func (c *NetworkMapComponents) getLocalResourceFirewallRules(targetPeerID string, policy *Policy) []*FirewallRule {
+	distributionPeers := c.getPoliciesSourcePeers([]*Policy{policy})
 
 	rules := make([]*FirewallRule, 0)
 	for _, rule := range policy.Rules {
 		if !rule.Enabled {
 			continue
@@
-		for _, peerID := range postureValidatedPeerIDs {
-			peer := c.GetPeerInfo(peerID)
-			if peer == nil {
-				continue
-			}
+		for _, peer := range c.getRulePeers(rule, policy.SourcePostureChecks, targetPeerID, distributionPeers) {
 			peerIP := peer.IP.String()
-				localResourceFwRule = append(localResourceFwRule, c.getLocalResourceFirewallRules(policy)...)
+				localResourceFwRule = append(localResourceFwRule, c.getLocalResourceFirewallRules(peerID, policy)...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/networkmap_components.go` around lines 746 - 799, The
code currently computes sourcePeerIDs once for the whole policy
(getPoliciesSourcePeers on the policy) then emits every enabled rule for that
union, letting peers from other rules (or disabled siblings) inherit
ports/protocols; fix by computing sourcePeerIDs and postureValidatedPeerIDs per
individual rule inside getLocalResourceFirewallRules: move the source-peer and
posture validation calls into the loop over policy.Rules (i.e., call
c.getPoliciesSourcePeers for the single rule or add a helper to get a rule's
sources), perform the rule.Enabled check before using those values, and then use
the per-rule postureValidatedPeerIDs when creating FirewallRule entries so each
rule only applies to its own sources.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@management/server/types/networkmap_components.go`:
- Around line 718-720: The branch uses the function-level isRoutingPeer flag
which can remain true after an earlier resource sets it, causing later resources
with resource.OnRoutingPeer to incorrectly append rules; change the condition so
it checks routing status for the current resource only (e.g., compute a
per-resource boolean like isRoutingPeerForResource by evaluating the peerID
against this resource's routing/network attributes, then use if
isRoutingPeerForResource && resource.OnRoutingPeer { localResourceFwRule =
append(localResourceFwRule, c.getLocalResourceFirewallRules(policy)...) }),
ensuring getLocalResourceFirewallRules is only called when the peer actually
routes for that specific resource.
- Around line 746-799: The code currently computes sourcePeerIDs once for the
whole policy (getPoliciesSourcePeers on the policy) then emits every enabled
rule for that union, letting peers from other rules (or disabled siblings)
inherit ports/protocols; fix by computing sourcePeerIDs and
postureValidatedPeerIDs per individual rule inside
getLocalResourceFirewallRules: move the source-peer and posture validation calls
into the loop over policy.Rules (i.e., call c.getPoliciesSourcePeers for the
single rule or add a helper to get a rule's sources), perform the rule.Enabled
check before using those values, and then use the per-rule
postureValidatedPeerIDs when creating FirewallRule entries so each rule only
applies to its own sources.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5dccafdb-61bc-46ea-9e3f-734729ad3ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 5483a7c and 7b0ef86.

📒 Files selected for processing (1)
  • management/server/types/networkmap_components.go

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 7, 2026

Quality Gate Passed Quality Gate passed

Issues
0 New issues
3 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants