Skip to content

feat(diff): add configuration diff tool for OPNsense XML comparison - #227

Merged
unclesp1d3r merged 3 commits into
mainfrom
200-feature-configuration-diff-tool-for-opnsense-xml-comparison-and-analysis
Feb 5, 2026
Merged

feat(diff): add configuration diff tool for OPNsense XML comparison#227
unclesp1d3r merged 3 commits into
mainfrom
200-feature-configuration-diff-tool-for-opnsense-xml-comparison-and-analysis

Conversation

@unclesp1d3r

Copy link
Copy Markdown
Member

Summary

Implements GitHub Issue #200: a content-aware, security-focused configuration diff tool for comparing OPNsense XML configuration files.

  • Content-aware comparison: Matches entities semantically (firewall rules by UUID, interfaces by name, DHCP reservations by MAC)
  • Security impact flagging: Flags changes with high/medium/low security impact (e.g., any-any rules = high)
  • Multiple output formats: Terminal (color-coded), Markdown (for documentation), JSON (for automation)
  • Section filtering: Compare specific sections with --section firewall,nat,interfaces
  • Security-only mode: Show only security-relevant changes with --security flag

Changes

New Files (14 files, 3424 lines)

  • cmd/diff.go - CLI command with Cobra framework
  • internal/diff/types.go - Core data structures (Change, Result, Section, ChangeType)
  • internal/diff/engine.go - Comparison orchestration
  • internal/diff/analyzer.go - Content-aware structural analysis for all OPNsense sections
  • internal/diff/formatters/terminal.go - Color-coded terminal output with Lipgloss
  • internal/diff/formatters/markdown.go - Markdown formatted output
  • internal/diff/formatters/json.go - JSON structured output
  • internal/diff/integration_test.go - Integration tests with real config files
  • Unit tests for all components

Sections Supported

  • Firewall rules (matched by UUID)
  • NAT/port forwards
  • Interfaces (matched by name)
  • VLANs
  • DHCP static mappings (matched by MAC)
  • Users
  • Static routes
  • System settings (hostname, domain, timezone, DNS)

Usage Examples

# Compare two configs with terminal output (default)
opndossier diff old-config.xml new-config.xml

# Generate markdown report
opndossier diff old-config.xml new-config.xml -f markdown -o changes.md

# Compare only firewall rules
opndossier diff old-config.xml new-config.xml --section firewall

# Show only security-relevant changes
opndossier diff old-config.xml new-config.xml --security

# Generate JSON for automation
opndossier diff old-config.xml new-config.xml -f json | jq '.changes[]'

Test plan

  • Unit tests for types, engine, analyzer, and all formatters
  • Integration tests with real OPNsense config files from testdata/
  • just ci-check passes (0 linting issues, all tests pass)
  • Manual testing with sample configs

Closes #200

🤖 Generated with Claude Code

Implements GitHub Issue #200: content-aware, security-focused comparison
of OPNsense configuration files with semantic change detection.

Key features:
- Compare firewall rules by UUID with field-level change tracking
- Compare interfaces by name with detailed property changes
- Compare static DHCP reservations by MAC address
- Detect NAT rules, VLANs, users, routes, and system settings
- Security impact flagging (high/medium/low) for sensitive changes
- Multiple output formats: terminal (color-coded), markdown, JSON
- Section filtering (--section firewall,nat,interfaces)
- Security-only mode (--security flag)

Package structure:
- internal/diff/engine.go: Orchestrates comparison workflow
- internal/diff/analyzer.go: Content-aware structural analysis
- internal/diff/types.go: Core data structures (Change, Result, Section)
- internal/diff/formatters/: Terminal, Markdown, and JSON formatters
- cmd/diff.go: CLI command with Cobra framework

Closes #200

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 4, 2026 04:26
@unclesp1d3r unclesp1d3r linked an issue Feb 4, 2026 that may be closed by this pull request
10 tasks
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 4, 2026
@coderabbitai

coderabbitai Bot commented Feb 4, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Adds a full diff command to compare OPNsense XML configs with terminal, Markdown, and JSON output
    • Section filtering, security-only mode, and output-to-file or stdout; results include metadata, summary counts, and security impact badges
    • Shell completions for format and section flags; improved in-command help and examples
  • Tests
    • Extensive unit, formatter, engine, analyzer, and integration tests validating comparisons, formatters, filtering, and metadata

Walkthrough

Adds a full diff feature: CLI command to compare two OPNsense XML configs, a diff engine and analyzer producing structured Change results, three output formatters (terminal, markdown, JSON), options for section filtering and security-only output, plus unit and integration tests.

Changes

Cohort / File(s) Summary
CLI Command
cmd/diff.go
Adds diff subcommand, flags (--format,--section,--security-only,--output), flag completions/validation, file parsing, engine invocation, timeout handling, and output routing to chosen formatter/file.
Core Types
internal/diff/types.go, internal/diff/types_test.go
Introduces Result/Change/Summary/Metadata/Options, ChangeType/Section/SecurityImpact enums, grouping/aggregation helpers, JSON tags and tests.
Analyzer
internal/diff/analyzer.go, internal/diff/analyzer_test.go
Implements Analyzer with Compare* methods for System, FirewallRules, NAT, Interfaces, VLANs, DHCP, Users, Routes; produces Change entries with SecurityImpact heuristics and extensive unit tests.
Engine
internal/diff/engine.go, internal/diff/engine_test.go
Adds Engine (NewEngine, Compare) orchestrating per-section dispatch, context cancellation, section/security filtering, metadata population and result aggregation; tested across scenarios.
Formatters — Terminal
internal/diff/formatters/terminal.go, internal/diff/formatters/terminal_test.go
TerminalFormatter with optional styling, deterministic output when styles disabled, section grouping, symbols and security badges; tests for presentation and counts.
Formatters — Markdown
internal/diff/formatters/markdown.go, internal/diff/formatters/markdown_test.go
MarkdownFormatter emitting header/metadata, summary table, ordered sections, collapsible details, pipe-escaping and badge rendering; tests verify ordering and escaping.
Formatters — JSON
internal/diff/formatters/json.go, internal/diff/formatters/json_test.go
JSONFormatter (pretty/compact), SetPretty toggle, encoder behavior and tests validating structure and counts.
Integration Tests & Testdata
internal/diff/integration_test.go, internal/diff/testdata/*
Integration tests (integration build tag) exercising parser + engine across sample XML pairs, section filtering, same-file comparisons, and pairwise runs.
Other tests & helpers
internal/diff/*_test.go, internal/diff/formatters/*_test.go
Multiple unit tests added across analyzer, engine, types and formatters to validate correctness and rendering behavior.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as CLI (cmd/diff)
    participant Parser as Parser
    participant Engine as Diff Engine
    participant Analyzer as Analyzer
    participant Formatter as Formatter
    participant Output

    User->>CLI: opndossier diff old.xml new.xml --format markdown
    CLI->>Parser: parse(old.xml), parse(new.xml)
    Parser-->>CLI: oldCfg, newCfg
    CLI->>Engine: NewEngine(oldCfg, newCfg, opts)
    CLI->>Engine: Compare(ctx)
    Engine->>Analyzer: CompareSection(System/Firewall/NAT/Interfaces/...)
    loop per section
        Analyzer-->>Engine: []Change
    end
    Engine->>Engine: apply section filter / security-only
    Engine-->>CLI: Result (Summary, Metadata, Changes)
    CLI->>Formatter: Format(result)
    Formatter->>Output: write to stdout/file
    Output-->>User: render result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

Two configs meet, parsed and parsed anew,
Analyzer whispers what changed and why,
Sections lined and badges lit for view,
Markdown, JSON, terminal—choose your sky.
A tidy diff to keep the network dry.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commit format with 'feat(diff):' prefix and clearly describes the main change: adding a configuration diff tool for OPNsense XML comparison.
Description check ✅ Passed The PR description is comprehensive, covering summary, changes, sections supported, usage examples, and test plan. It aligns well with the template's key sections (Description, Type of Change, Related Issues, Testing).
Linked Issues check ✅ Passed The PR fully implements the core objectives of Issue #200: semantic comparison engine, section filtering, security-impact flagging, multiple formatters (terminal/markdown/JSON), and CLI integration with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with Issue #200 objectives. The 14 new files (3424 lines) implement the diff tool core components, formatters, and tests without introducing unrelated modifications.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 200-feature-configuration-diff-tool-for-opnsense-xml-comparison-and-analysis

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

@dosubot dosubot Bot added enhancement New feature or request security Security-related features and issues labels Feb 4, 2026
@codecov

codecov Bot commented Feb 4, 2026

Copy link
Copy Markdown

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.

Pull request overview

This PR implements GitHub Issue #200, adding a content-aware configuration diff tool for comparing OPNsense XML files. The implementation provides semantic comparison of configuration elements (matching firewall rules by UUID, interfaces by name, DHCP reservations by MAC), security impact flagging, and multiple output formats (terminal, markdown, JSON).

Changes:

  • Adds a complete diff engine with semantic analysis for all major OPNsense configuration sections (system, firewall, NAT, interfaces, VLANs, DHCP, users, routing)
  • Implements three output formatters (terminal with color support, markdown for documentation, JSON for automation) with proper TERM=dumb support for CI environments
  • Provides CLI command with section filtering (--section) and security-only mode (--security) flags
  • Includes comprehensive test coverage with unit tests for all components and integration tests with real config files

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/diff/types.go Core data structures (Change, Result, Section, ChangeType) with JSON serialization
internal/diff/types_test.go Unit tests for types with comprehensive coverage of all methods
internal/diff/engine.go Comparison orchestration with context cancellation and section filtering
internal/diff/engine_test.go Engine tests covering identical configs, changes, and context cancellation
internal/diff/analyzer.go Content-aware structural analysis for all OPNsense sections (745 lines)
internal/diff/analyzer_test.go Comprehensive analyzer tests with multiple scenarios per section
internal/diff/integration_test.go Integration tests with real config files and build tag isolation
internal/diff/formatters/terminal.go Color-coded terminal output with lipgloss styling and TERM=dumb support
internal/diff/formatters/terminal_test.go Terminal formatter tests with style toggling for verification
internal/diff/formatters/markdown.go Markdown output with tables, collapsible details, and special character escaping
internal/diff/formatters/markdown_test.go Markdown formatter tests including pipe character escaping
internal/diff/formatters/json.go JSON formatter with pretty and compact modes
internal/diff/formatters/json_test.go JSON formatter tests validating output structure
cmd/diff.go CLI command with comprehensive help, flag validation, and shell completion

Comment thread internal/diff/analyzer.go Outdated
Comment on lines +133 to +139
changes = append(changes, Change{
Type: ChangeModified,
Section: SectionFirewall,
Path: fmt.Sprintf("filter.rule[uuid=%s]", uuid),
Description: "Modified rule: " + ruleDescription(newRule),
OldValue: formatRule(oldRule),
NewValue: formatRule(newRule),

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Missing security impact assessment for modified firewall rules. When a firewall rule is modified (line 132-141), no SecurityImpact is assigned, unlike added rules (which check for permissive rules on line 121-122) and removed rules (which always get "medium" on line 111). A modified rule could have security implications if it becomes more permissive (e.g., changing from specific port to any port, or from specific source to any source). Consider adding security impact analysis for modifications similar to the logic used for added rules.

Suggested change
changes = append(changes, Change{
Type: ChangeModified,
Section: SectionFirewall,
Path: fmt.Sprintf("filter.rule[uuid=%s]", uuid),
Description: "Modified rule: " + ruleDescription(newRule),
OldValue: formatRule(oldRule),
NewValue: formatRule(newRule),
impact := ""
// Flag cases where the modified rule becomes permissive while the old rule was not.
if isPermissiveRule(newRule) && !isPermissiveRule(oldRule) {
impact = "high"
}
changes = append(changes, Change{
Type: ChangeModified,
Section: SectionFirewall,
Path: fmt.Sprintf("filter.rule[uuid=%s]", uuid),
Description: "Modified rule: " + ruleDescription(newRule),
OldValue: formatRule(oldRule),
NewValue: formatRule(newRule),
SecurityImpact: impact,

Copilot uses AI. Check for mistakes.
Comment thread internal/diff/analyzer.go
"slices"
"strings"

"github.com/EvilBit-Labs/opnDossier/internal/schema"

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Package import inconsistency within the diff module. The implementation files use different packages: internal/diff/engine.go and internal/diff/integration_test.go use internal/model, while internal/diff/analyzer.go and test files use internal/schema. While both work (model re-exports schema types), this inconsistency reduces code maintainability. Consider standardizing on one package throughout the diff module. Since internal/schema is the canonical package (see internal/model/system.go:1 which states "Package model re-exports types from internal/schema for backward compatibility"), using internal/schema consistently would be preferred.

Copilot uses AI. Check for mistakes.
Comment thread internal/diff/integration_test.go Outdated
// Get all sample config files
configs := []string{}
for i := 1; i <= 7; i++ {
path := filepath.Join(testdataDir, "sample.config."+string(rune('0'+i))+".xml")

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

String concatenation bug in filename generation. The expression string(rune('0'+i)) converts an integer to a rune character incorrectly. When i=1, this gives '0'+1 = '1' (ASCII 49), but when i=7, it gives '0'+7 = '7' (ASCII 55). This happens to work for single digits 1-7, but the logic is fragile. Use fmt.Sprintf("sample.config.%d.xml", i) instead for clarity and correctness.

Copilot uses AI. Check for mistakes.
Comment thread internal/diff/engine.go Outdated
Comment on lines +14 to +15
// Version is the tool version included in metadata.
const Version = "1.0.0"

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

The Version constant is duplicated. A canonical version constant already exists in internal/constants/constants.go (see line 7). According to project standards, version information should be centralized. Replace this local constant with constants.Version by importing "github.com/EvilBit-Labs/opnDossier/internal/constants" and using constants.Version on line 42 instead of the local Version constant.

Copilot uses AI. Check for mistakes.
Comment thread cmd/diff.go Outdated
Comment on lines +261 to +277
"firewall",
"nat",
"interfaces",
"vlans",
"dhcp",
"users",
"routing",
"dns",
"vpn",
"certificates",
}
for _, s := range diffSections {
if !slices.Contains(validSections, strings.ToLower(s)) {
return fmt.Errorf("invalid section %q, must be one of: %s",
s, strings.Join(validSections, ", "))
}
}

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

Mismatch between section validation and shell completion. The validSections list here includes: system, firewall, nat, interfaces, vlans, dhcp, users, routing, dns, vpn, certificates. However, the ValidSections completion function in cmd/shared_flags.go:188-196 only provides completions for: system, network, firewall, services, security. This inconsistency will confuse users as the shell completion won't suggest valid sections like "nat", "interfaces", "vlans", etc. Either update the ValidSections function to match these sections, or create a new diff-specific completion function that's registered on line 67.

Suggested change
"firewall",
"nat",
"interfaces",
"vlans",
"dhcp",
"users",
"routing",
"dns",
"vpn",
"certificates",
}
for _, s := range diffSections {
if !slices.Contains(validSections, strings.ToLower(s)) {
return fmt.Errorf("invalid section %q, must be one of: %s",
s, strings.Join(validSections, ", "))
}
}
"network",
"firewall",
"services",
"security",
}
for _, s := range diffSections {
if !slices.Contains(validSections, strings.ToLower(s)) {
return fmt.Errorf("invalid section %q, must be one of: %s",
s, strings.Join(validSections, ", "))
}
}

Copilot uses AI. Check for mistakes.
@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation go Pull requests that update go code priority:normal Normal priority issue testing Test infrastructure and test-related issues type:feature New feature implementation labels Feb 4, 2026
@unclesp1d3r unclesp1d3r self-assigned this Feb 4, 2026
- Add security impact assessment for modified firewall rules that become
  more permissive (high impact when rule changes from restrictive to
  any-any)
- Use constants.Version instead of duplicate local Version constant
- Fix string concatenation bug in integration test (use fmt.Sprintf)
- Add ValidDiffSections completion function with all diff-specific
  sections to fix mismatch with shell completion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the audit Phase 4.3 tasks - Audit Report Generation label Feb 4, 2026
Critical fixes:
- Add file.Sync() before close in outputDiffResult to prevent data loss
- Add nil pointer checks to all analyzer comparison functions

Important improvements:
- Validate and reject unimplemented sections (dns, vpn, certificates)
- Fix Interface.Get() return handling (bool, not error)
- Fix rulesEqual to include Interface field comparison
- Add explicit switch cases for unimplemented sections in engine

Type safety enhancements:
- Add IsValid() method for ChangeType validation
- Add ImplementedSections(), IsValid(), IsImplemented() for Section
- Add SecurityImpact enum type with High/Medium/Low constants

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

audit Phase 4.3 tasks - Audit Report Generation documentation Improvements or additions to documentation enhancement New feature or request go Pull requests that update go code priority:normal Normal priority issue security Security-related features and issues size:XXL This PR changes 1000+ lines, ignoring generated files. testing Test infrastructure and test-related issues type:feature New feature implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Configuration Diff Tool for OPNsense XML Comparison and Analysis

2 participants