Skip to content

fix: Forti changes not correctly propagated#4722

Merged
Y4nnikH merged 34 commits into
CactuseSecurity:developfrom
weichwaren-schmiede:4446-fortimanager-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
Jun 22, 2026
Merged

fix: Forti changes not correctly propagated#4722
Y4nnikH merged 34 commits into
CactuseSecurity:developfrom
weichwaren-schmiede:4446-fortimanager-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone

Conversation

@Laennart

@Laennart Laennart commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

closes #4446

Y4nnikH and others added 15 commits April 12, 2026 19:43
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…r-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
@Laennart Laennart self-assigned this Jun 7, 2026
@Laennart Laennart added the bug Something isn't working label Jun 7, 2026
@Laennart Laennart marked this pull request as ready for review June 7, 2026 12:25
Copilot AI review requested due to automatic review settings June 7, 2026 12:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Laennart Laennart requested a review from Y4nnikH June 7, 2026 12:29

@tpurschke tpurschke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

careful - if memory serves me right, we are dealing with multiple src/dst zones per rule now and are therefore using a cross-ref table.
imho all zone fields in rule should be removed instead.
@Imat00 - can you comment on this?

@Imat00

Imat00 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Yes, the zone assignments are stored in the cross-reference tables (rule_from_zone / rule_to_zone). A rule can have multiple source and destination zones, and changes are tracked via created / removed.

I think the issue in #4446 relates to how these changes are propagated to the changelog / removal handling.

@Y4nnikH

Y4nnikH commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

careful - if memory serves me right, we are dealing with multiple src/dst zones per rule now and are therefore using a cross-ref table. imho all zone fields in rule should be removed instead. @Imat00 - can you comment on this?

yes, a rule can have multiple src/dst zones, just as it can have multiple src/dst network objects. so we should use the same pattern: zone uids in the rule, joined by "|", as well as a crossref table which links the database ids. this is especially needed to fix the issue #4446, because if the uids are not in the rule, the rule object does not change for added/removed zones and we cannot record this arguably security-relevant change by creating a changelog rule entry

Y4nnikH and others added 5 commits June 8, 2026 18:14
…imanager-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…t-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…t-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone
…t-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone

@tpurschke tpurschke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Key issue — zone-string ordering is inconsistent across the three producers

The same logical value is built three different ways:

Producer | Sort | Dedup -- | -- | -- Python importer (fos_rule.py:107, fmgr_rule.py:201) — LIST_DELIMITER.join(names) | ❌ interface order | ❌ Migration backfill (9.1.9.sql) — string_agg(DISTINCT z.zone_name ... ORDER BY z.zone_name) | ✅ alphabetical | ✅ C# NormalizedRule (NormalizedRule.cs:128) — `string.Join(" | ", ...Order())` | ✅ alphabetical

Because equality now includes these fields, the mismatch matters:

  • Spurious changes on first import after upgrade. The backfill writes sorted/deduped strings; the next Forti import normalizes in interface order. For any rule with ≥2 zones where interface order ≠ alphabetical (or with duplicate zones), __eq__ returns false → the rule is flagged as changed and a changelog "C" entry is written, even though nothing actually changed. On large FortiManager imports this could be a one-time burst of false change entries — ironic given this PR is about change-detection accuracy.
  • Single-zone rules (the common case) are unaffected, which is why it may pass casual testing.

Recommendation: make all three produce a canonical form — sort and dedup. The cheapest fix is in the Python normalization (e.g. LIST_DELIMITER.join(sorted(set(rule_src_zone_names)))), and add .Distinct() to the C# side, so all three match DISTINCT ... ORDER BY. Do confirm whether zone order is itself ever semantically meaningful on Forti — if it is, the backfill is the one that's wrong and should preserve order instead; but the link table likely can't reconstruct order anyway, so canonical-sorted everywhere is the pragmatic choice.

Smaller points

  • Dead int columns / leftover TODO. Keeping rule_from_zone/rule_to_zone (int) on the rule table is reasonably justified in the SQL comment, but prepare_rule_for_import previously set them and the Rule model now drops them entirely — so those columns are now never written by the importer. Fine, but it leaves real tech debt; consider a tracked follow-up issue rather than the informal "for now" comment, since the link tables remain the source of truth for resolved zones.
  • .agents submodule bump (c677fd6…  74356aa…) appears unrelated to this fix. Unless intentional, drop it from the PR to keep the change focused.
  • Migration safety is good: ADD COLUMN IF NOT EXISTS, to_regclass guards, and COALESCE(...) <> ... to avoid no-op updates. Note the guards check public.rule_from_zone/rule_to_zone as relations (to_regclass) — confirm those link tables are actually tables/views by those exact names in all deployments, otherwise the backfill silently RETURNs and leaves zones NULL (then the next import re-detects everything as changed — same symptom as above, larger blast radius).
  • No tests. CLAUDE.md requires unit tests for new behavior, and roles/importer/files/importer/test already exists. Given the equality change is the crux of the fix, a test asserting that two RuleNormalized differing only in rule_src_zone/rule_dst_zone are now unequal (and flagged security-relevant) would lock in the fix and guard against regressions. Worth adding before merge.
  • Hasura metadata: the column was added in 9 permission blocks — verify those cover the insert/update permission set for the importer role specifically (not just select), otherwise the new columns won't be writable on import.

Verdict

Correct, well-targeted fix for #4446, with clean and defensively-written migration. The one thing I'd block on is the zone-string ordering inconsistency between the importer, the backfill, and the C# normalizer — without canonicalization the next import after upgrade can emit false "changed" entries, undercutting the very feature this PR delivers. Add a regression test and drop the stray .agents bump.

Key issue — zone-string ordering is inconsistent across the three producers The same logical value is built three different ways:

Producer Sort Dedup
Python importer (fos_rule.py:107, fmgr_rule.py:201) — LIST_DELIMITER.join(names) ❌ interface order ❌
Migration backfill (9.1.9.sql) — string_agg(DISTINCT z.zone_name ... ORDER BY z.zone_name) ✅ alphabetical ✅
C# NormalizedRule (NormalizedRule.cs:128) — string.Join(" ", ...Order()) ✅ alphabetical
Because equality now includes these fields, the mismatch matters:

Spurious changes on first import after upgrade. The backfill writes sorted/deduped strings; the next Forti import normalizes in interface order. For any rule with ≥2 zones where interface order ≠ alphabetical (or with duplicate zones), eq returns false → the rule is flagged as changed and a changelog "C" entry is written, even though nothing actually changed. On large FortiManager imports this could be a one-time burst of false change entries — ironic given this PR is about change-detection accuracy.
Single-zone rules (the common case) are unaffected, which is why it may pass casual testing.
Recommendation: make all three produce a canonical form — sort and dedup. The cheapest fix is in the Python normalization (e.g. LIST_DELIMITER.join(sorted(set(rule_src_zone_names)))), and add .Distinct() to the C# side, so all three match DISTINCT ... ORDER BY. Do confirm whether zone order is itself ever semantically meaningful on Forti — if it is, the backfill is the one that's wrong and should preserve order instead; but the link table likely can't reconstruct order anyway, so canonical-sorted everywhere is the pragmatic choice.

Smaller points
Dead int columns / leftover TODO. Keeping rule_from_zone/rule_to_zone (int) on the rule table is reasonably justified in the SQL comment, but prepare_rule_for_import previously set them and the Rule model now drops them entirely — so those columns are now never written by the importer. Fine, but it leaves real tech debt; consider a tracked follow-up issue rather than the informal "for now" comment, since the link tables remain the source of truth for resolved zones.
.agents submodule bump (c677fd6… → 74356aa…) appears unrelated to this fix. Unless intentional, drop it from the PR to keep the change focused.
Migration safety is good: ADD COLUMN IF NOT EXISTS, to_regclass guards, and COALESCE(...) <> ... to avoid no-op updates. Note the guards check public.rule_from_zone/rule_to_zone as relations (to_regclass) — confirm those link tables are actually tables/views by those exact names in all deployments, otherwise the backfill silently RETURNs and leaves zones NULL (then the next import re-detects everything as changed — same symptom as above, larger blast radius).
No tests. CLAUDE.md requires unit tests for new behavior, and roles/importer/files/importer/test already exists. Given the equality change is the crux of the fix, a test asserting that two RuleNormalized differing only in rule_src_zone/rule_dst_zone are now unequal (and flagged security-relevant) would lock in the fix and guard against regressions. Worth adding before merge.
Hasura metadata: the column was added in 9 permission blocks — verify those cover the insert/update permission set for the importer role specifically (not just select), otherwise the new columns won't be writable on import.
Verdict
Correct, well-targeted fix for #4446, with clean and defensively-written migration. The one thing I'd block on is the zone-string ordering inconsistency between the importer, the backfill, and the C# normalizer — without canonicalization the next import after upgrade can emit false "changed" entries, undercutting the very feature this PR delivers. Add a regression test and drop the stray .agents bump.

@Laennart

Copy link
Copy Markdown
Collaborator Author

@tpurschke I added an issue to track the dead columns: #4789

@Laennart Laennart requested a review from tpurschke June 20, 2026 13:00
@sonarqubecloud

Copy link
Copy Markdown

@Y4nnikH Y4nnikH left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

reviewed and tested on customer dev machine

@Y4nnikH Y4nnikH merged commit e080f1c into CactuseSecurity:develop Jun 22, 2026
3 checks passed
@Y4nnikH Y4nnikH deleted the 4446-fortimanager-changes-in-fromto-do-not-create-new-rules-or-mark-entries-as-removed-in-rule_x_zone branch June 22, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants